Hệ thống xếp lịch học tín chỉ cho sinh viên CNTT trên PHP & MySQL
111.111 lượt xem;
- common.js.php
- project /
1 <?php
2 if(!defined('datalist_db_encoding')) define('datalist_db_encoding', 'UTF-8');
3 if(function_exists('date_default_timezone_set')) @date_default_timezone_set('America/New_York');
4
5 /* force caching */
6 $last_modified = filemtime(__FILE__);
7 $last_modified_gmt = gmdate('D, d M Y H:i:s', $last_modified) . ' GMT';
8 $headers = (function_exists('getallheaders') ? getallheaders() : $_SERVER);
9 if(isset($headers['If-Modified-Since']) && (strtotime($headers['If-Modified-Since']) == $last_modified)){
10 @header("Last-Modified: {$last_modified_gmt}", true, 304);
11 @header("Cache-Control: public, max-age=240", true);
12 exit;
13 }
14
15 @header("Last-Modified: {$last_modified_gmt}", true, 200);
16 @header("Cache-Control: public, max-age=240", true);
17 @header('Content-Type: text/javascript; charset=' . datalist_db_encoding);
18 $currDir = dirname(__FILE__);
19 include("{$currDir}/defaultLang.php");
20 include("{$currDir}/language.php");
21 ?>
22 var AppGini = AppGini || {};
23 AppGini.ajaxCache = function(){
24 var _tests = [];
25
26 /*
27 An array of functions that receive a parameterless url and a parameters object,
28 makes a test,
29 and if test passes, executes something and/or
30 returns a non-false value if test passes,
31 or false if test failed (useful to tell if tests should continue or not)
32 */
33 var addCheck = function(check){ /* */
34 if(typeof(check) == 'function'){
35 _tests.push(check);
36 }
37 };
38
39 var _jqAjaxData = function(opt){ /* */
40 var opt = opt || {};
41 var url = opt.url || '';
42 var data = opt.data || {};
43
44 var params = url.match(/\?(.*)$/);
45 var param = (params !== null ? params[1] : '');
46
47 var sPageURL = decodeURIComponent(param),
48 sURLVariables = sPageURL.split('&'),
49 sParameter,
50 i;
51
52 for(i = 0; i < sURLVariables.length; i++){
53 sParameter = sURLVariables[i].split('=');
54 if(sParameter[0] == '') continue;
55 data[sParameter[0]] = sParameter[1] || '';
56 }
57
58 return data;
59 };
60
61 var start = function(){ /* */
62 if(!_tests.length) return; // no need to monitor ajax requests since no checks were defined
63 var reqTests = _tests;
64 $j.ajaxPrefilter(function(options, originalOptions, jqXHR){
65 var success = originalOptions.success || $j.noop,
66 data = _jqAjaxData(originalOptions),
67 oUrl = originalOptions.url || '',
68 url = oUrl.match(/\?/) ? oUrl.match(/(.*)\?/)[1] : oUrl;
69
70 options.beforeSend = function(){ /* */
71 var req, cached = false, resp;
72
73 for(var i = 0; i < reqTests.length; i++){
74 resp = reqTests[i](url, data);
75 if(resp === false) continue;
76
77 success(resp);
78 return false;
79 }
80
81 return true;
82 }
83 });
84 };
85
86 return {
87 addCheck: addCheck,
88 start: start
89 };
90 };
91
92 /* initials and fixes */
93 jQuery(function(){
94 AppGini.count_ajaxes_blocking_saving = 0;
95
96 /* add ":truncated" pseudo-class to detect elements with clipped text */
97 $j.expr[':'].truncated = function(obj){
98 var $this = $j(obj);
99 var $c = $this
100 .clone()
101 .css({ display: 'inline', width: 'auto', visibility: 'hidden', 'padding-right': 0 })
102 .css({ 'font-size': $this.css('font-size') })
103 .appendTo('body');
104
105 var e_width = $this.outerWidth();
106 var c_width = $c.outerWidth();
107 $c.remove();
108
109 return ( c_width > e_width );
110 };
111
112 var fix_lookup_width = function(field){
113 var s2 = $j('div.select2-container[id=s2id_' + field + '-container]');
114 if(!s2.length) return;
115
116 var s2new_width = 0, s2view_width = 0, s2parent_width = 0;
117
118 var s2new = s2.parent().find('.add_new_parent:visible');
119 var s2view = s2.parent().find('.view_parent:visible');
120 if(s2new.length) s2new_width = s2new.outerWidth(true);
121 if(s2view.length) s2view_width = s2view.outerWidth(true);
122 s2parent_width = s2.parent().innerWidth();
123
124 // console.log({ s2new_width: s2new_width, s2view_width: s2view_width, s2parent_width: s2parent_width });
125
126 s2.css({ width: '100%', 'max-width': (s2parent_width - s2new_width - s2view_width - 1) + 'px' });
127 }
128
129 $j(window).resize(function(){
130 var window_width = $j(window).width();
131 var max_width = $j('body').width() * 0.5;
132
133 $j('.select2-container:not(.option_list)').each(function(){
134 var field = $j(this).attr('id').replace(/^s2id_/, '').replace(/-container$/, '');
135 fix_lookup_width(field);
136 });
137
138 //fix_table_responsive_width();
139
140 var full_img_factor = 0.9; /* xs */
141 if(window_width >= 992) full_img_factor = 0.6; /* md, lg */
142 else if(window_width >= 768) full_img_factor = 0.9; /* sm */
143
144 $j('.detail_view .img-responsive').css({'max-width' : parseInt($j('.detail_view').width() * full_img_factor) + 'px'});
145
146 /* remove labels from truncated buttons, leaving only glyphicons */
147 $j('.btn.truncate:truncated').each(function(){
148 // hide text
149 var label = $j(this).html();
150 var mlabel = label.replace(/.*(<i.*?><\/i>).*/, '$1');
151 $j(this).html(mlabel);
152 });
153 });
154
155 setTimeout(function(){ /* */ $j(window).resize(); }, 1000);
156 setTimeout(function(){ /* */ $j(window).resize(); }, 3000);
157
158 /* don't allow saving detail view when there's an ajax request to a url that matches the following */
159 var ajax_blockers = new RegExp(/(ajax_combo\.php|_autofill\.php|ajax_check_unique\.php)/);
160 $j(document).ajaxSend(function(e, r, s){
161 if(s.url.match(ajax_blockers)){
162 AppGini.count_ajaxes_blocking_saving++;
163 $j('#update, #insert').prop('disabled', true);
164 }
165 });
166 $j(document).ajaxComplete(function(e, r, s){
167 if(s.url.match(ajax_blockers)){
168 AppGini.count_ajaxes_blocking_saving = Math.max(AppGini.count_ajaxes_blocking_saving - 1, 0);
169 if(AppGini.count_ajaxes_blocking_saving <= 0)
170 $j('#update, #insert').prop('disabled', false);
171 }
172 });
173
174 /* don't allow responsive images to initially exceed the smaller of their actual dimensions, or .6 container width */
175 jQuery('.detail_view .img-responsive').each(function(){
176 var pic_real_width, pic_real_height;
177 var img = jQuery(this);
178 jQuery('<img/>') // Make in memory copy of image to avoid css issues
179 .attr('src', img.attr('src'))
180 .load(function() {
181 pic_real_width = this.width;
182 pic_real_height = this.height;
183
184 if(pic_real_width > $j('.detail_view').width() * .6) pic_real_width = $j('.detail_view').width() * .6;
185 img.css({ "max-width": pic_real_width });
186 });
187 });
188
189 jQuery('.table-responsive .img-responsive').each(function(){
190 var pic_real_width, pic_real_height;
191 var img = jQuery(this);
192 jQuery('<img/>') // Make in memory copy of image to avoid css issues
193 .attr('src', img.attr('src'))
194 .load(function() {
195 pic_real_width = this.width;
196 pic_real_height = this.height;
197
198 if(pic_real_width > $j('.table-responsive').width() * .6) pic_real_width = $j('.table-responsive').width() * .6;
199 img.css({ "max-width": pic_real_width });
200 });
201 });
202
203 /* toggle TV action buttons based on selected records */
204 jQuery('.record_selector').click(function(){
205 var id = jQuery(this).val();
206 var checked = jQuery(this).prop('checked');
207 update_action_buttons();
208 });
209
210 /* select/deselect all records in TV */
211 jQuery('#select_all_records').click(function(){
212 jQuery('.record_selector').prop('checked', jQuery(this).prop('checked'));
213 update_action_buttons();
214 });
215
216 /* fix behavior of select2 in bootstrap modal. See: https://github.com/ivaynberg/select2/issues/1436 */
217 jQuery.fn.modal.Constructor.prototype.enforceFocus = function(){ /* */ };
218
219 /* remove empty navbar menus */
220 $j('nav li.dropdown').each(function(){
221 var num_items = $j(this).children('.dropdown-menu').children('li').length;
222 if(!num_items) $j(this).remove();
223 })
224
225 update_action_buttons();
226
227 /* remove empty images and links from TV, TVP */
228 $j('.table a[href="<?php echo $Translation['ImageFolder']; ?>"], .table img[src="<?php echo $Translation['ImageFolder']; ?>"]').remove();
229
230 /* remove empty email links from TV, TVP */
231 $j('a[href="mailto:"]').remove();
232
233 /* Disable action buttons when form is submitted to avoid user re-submission on slow connections */
234 $j('form').eq(0).submit(function(){
235 setTimeout(function(){
236 $j('#insert, #update, #delete, #deselect').prop('disabled', true);
237 }, 200); // delay purpose is to allow submitting the button values first then disable them.
238 });
239
240 /* fix links inside alerts */
241 $j('.alert a:not(.btn)').addClass('alert-link');
242 });
243
244 /* show/hide TV action buttons based on whether records are selected or not */
245 function update_action_buttons(){
246 if(jQuery('.record_selector:checked').length){
247 jQuery('.selected_records').removeClass('hidden');
248 jQuery('#select_all_records')
249 .prop('checked', (jQuery('.record_selector:checked').length == jQuery('.record_selector').length));
250 }else{
251 jQuery('.selected_records').addClass('hidden');
252 }
253 }
254
255 /* fix table-responsive behavior on Chrome */
256 function fix_table_responsive_width(){
257 var resp_width = jQuery('div.table-responsive').width();
258 var table_width;
259
260 if(resp_width){
261 jQuery('div.table-responsive table').width('100%');
262 table_width = jQuery('div.table-responsive table').width();
263 resp_width = jQuery('div.table-responsive').width();
264 if(resp_width == table_width){
265 jQuery('div.table-responsive table').width(resp_width - 1);
266 }
267 }
268 }
269
270 function schools_validateData(){
271 $j('.has-error').removeClass('has-error');
272 /* Field name can't be empty */
273 if($j('#name').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Name", close: function(){ /* */ $j('[name=name]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
274 return true;
275 }
276 function departments_validateData(){
277 $j('.has-error').removeClass('has-error');
278 /* Field name can't be empty */
279 if($j('#name').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Name", close: function(){ /* */ $j('[name=name]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
280 /* Field school can't be empty */
281 if($j('#school').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> School", close: function(){ /* */ $j('[name=school]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
282 return true;
283 }
284 function class_time_table_validateData(){
285 $j('.has-error').removeClass('has-error');
286 /* Field day can't be empty */
287 if($j('#day').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Day", close: function(){ /* */ $j('[name=day]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
288 /* Field time_start can't be empty */
289 if($j('#time_start').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Time Start", close: function(){ /* */ $j('[name=time_start]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
290 /* Field time_end can't be empty */
291 if($j('#time_end').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Time End", close: function(){ /* */ $j('[name=time_end]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
292 /* Field unit_code can't be empty */
293 if($j('#unit_code').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Unit code", close: function(){ /* */ $j('[name=unit_code]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
294 /* Field venue can't be empty */
295 if($j('#venue').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Venue", close: function(){ /* */ $j('[name=venue]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
296 /* Field school can't be empty */
297 if($j('#school').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> School", close: function(){ /* */ $j('[name=school]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
298 /* Field department can't be empty */
299 if($j('#department').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Department", close: function(){ /* */ $j('[name=department]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
300 /* Field year_of_study can't be empty */
301 if($j('#year_of_study').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Year of study", close: function(){ /* */ $j('[name=year_of_study]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
302 return true;
303 }
304 function exam_time_table_validateData(){
305 $j('.has-error').removeClass('has-error');
306 /* Date field date can't be empty */
307 if($j('#date-dd').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Date", close: function(){ /* */ $j('#date-dd').focus().parents('.form-group').addClass('has-error'); } }); return false; };
308 if($j('#date-mm').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Date", close: function(){ /* */ $j('#date-mm').focus().parents('.form-group').addClass('has-error'); } }); return false; };
309 if($j('#date').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Date", close: function(){ /* */ $j('#date').focus().parents('.form-group').addClass('has-error'); } }); return false; };
310 /* Field time_start can't be empty */
311 if($j('#time_start').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Time Start", close: function(){ /* */ $j('[name=time_start]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
312 /* Field time_end can't be empty */
313 if($j('#time_end').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Time End", close: function(){ /* */ $j('[name=time_end]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
314 /* Field unit_code can't be empty */
315 if($j('#unit_code').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Unit code", close: function(){ /* */ $j('[name=unit_code]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
316 /* Field venue can't be empty */
317 if($j('#venue').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Venue", close: function(){ /* */ $j('[name=venue]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
318 /* Field school can't be empty */
319 if($j('#school').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> School", close: function(){ /* */ $j('[name=school]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
320 /* Field department can't be empty */
321 if($j('#department').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Department", close: function(){ /* */ $j('[name=department]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
322 /* Field year_of_study can't be empty */
323 if($j('#year_of_study').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Year of study", close: function(){ /* */ $j('[name=year_of_study]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
324 return true;
325 }
326 function personal_time_table_validateData(){
327 $j('.has-error').removeClass('has-error');
328 /* Field day can't be empty */
329 if($j('#day').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Day", close: function(){ /* */ $j('[name=day]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
330 /* Field time_start can't be empty */
331 if($j('#time_start').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Time Start", close: function(){ /* */ $j('[name=time_start]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
332 /* Field time_end can't be empty */
333 if($j('#time_end').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Time End", close: function(){ /* */ $j('[name=time_end]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
334 /* Field activity can't be empty */
335 if($j('#activity').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Activity", close: function(){ /* */ $j('[name=activity]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
336 return true;
337 }
338 function student_details_validateData(){
339 $j('.has-error').removeClass('has-error');
340 /* Field full_name can't be empty */
341 if($j('#full_name').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Full name", close: function(){ /* */ $j('[name=full_name]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
342 /* Field school can't be empty */
343 if($j('#school').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> School", close: function(){ /* */ $j('[name=school]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
344 /* Field department can't be empty */
345 if($j('#department').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Department", close: function(){ /* */ $j('[name=department]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
346 /* Field year_of_study can't be empty */
347 if($j('#year_of_study').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Year of study", close: function(){ /* */ $j('[name=year_of_study]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
348 /* Field reg_no can't be empty */
349 if($j('#reg_no').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Reg no", close: function(){ /* */ $j('[name=reg_no]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
350 return true;
351 }
352 function notices_validateData(){
353 $j('.has-error').removeClass('has-error');
354 /* Field notice can't be empty */
355 if($j('#notice').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Notice", close: function(){ /* */ $j('[name=notice]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
356 /* Field school can't be empty */
357 if($j('#school').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> School", close: function(){ /* */ $j('[name=school]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
358 /* Field department can't be empty */
359 if($j('#department').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Department", close: function(){ /* */ $j('[name=department]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
360 /* Field year_of_study can't be empty */
361 if($j('#year_of_study').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Year of study", close: function(){ /* */ $j('[name=year_of_study]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
362 return true;
363 }
364
365 function post(url, params, update, disable, loading, success_callback){
366 $j.ajax({
367 url: url,
368 type: 'POST',
369 data: params,
370 beforeSend: function() {
371 if($j('#' + disable).length) $j('#' + disable).prop('disabled', true);
372 if($j('#' + loading).length && update != loading) $j('#' + loading).html('<div style="direction: ltr;"><img src="loading.gif"> <?php echo addslashes($Translation['Loading ...']); ?></div>');
373 },
374 success: function(resp) {
375 if($j('#' + update).length) $j('#' + update).html(resp);
376 if(success_callback != undefined) success_callback();
377 },
378 complete: function() {
379 if($j('#' + disable).length) $j('#' + disable).prop('disabled', false);
380 if($j('#' + loading).length && loading != update) $j('#' + loading).html('');
381 }
382 });
383 }
384
385 function post2(url, params, notify, disable, loading, redirectOnSuccess){
386 new Ajax.Request(
387 url, {
388 method: 'post',
389 parameters: params,
390 onCreate: function() {
391 if($(disable) != undefined) $(disable).disabled=true;
392 if($(loading) != undefined) $(loading).show();
393 },
394 onSuccess: function(resp) {
395 /* show notification containing returned text */
396 if($(notify) != undefined) $(notify).removeClassName('Error').appear().update(resp.responseText);
397
398 /* in case no errors returned, */
399 if(!resp.responseText.match(/<?php echo $Translation['error:']; ?>/)){
400 /* redirect to provided url */
401 if(redirectOnSuccess != undefined){
402 window.location=redirectOnSuccess;
403
404 /* or hide notification after a few seconds if no url is provided */
405 }else{
406 if($(notify) != undefined) window.setTimeout(function(){ /* */ $(notify).fade(); }, 15000);
407 }
408
409 /* in case of error, apply error class */
410 }else{
411 $(notify).addClassName('Error');
412 }
413 },
414 onComplete: function() {
415 if($(disable) != undefined) $(disable).disabled=false;
416 if($(loading) != undefined) $(loading).hide();
417 }
418 }
419 );
420 }
421 function passwordStrength(password, username){
422 // score calculation (out of 10)
423 var score = 0;
424 re = new RegExp(username, 'i');
425 if(username.length && password.match(re)) score -= 5;
426 if(password.length < 6) score -= 3;
427 else if(password.length > 8) score += 5;
428 else score += 3;
429 if(password.match(/(.*[0-9].*[0-9].*[0-9])/)) score += 3;
430 if(password.match(/(.*[!,@,#,$,%,^,&,*,?,_,~].*[!,@,#,$,%,^,&,*,?,_,~])/)) score += 5;
431 if(password.match(/([a-z].*[A-Z])|([A-Z].*[a-z])/)) score += 2;
432
433 if(score >= 9)
434 return 'strong';
435 else if(score >= 5)
436 return 'good';
437 else
438 return 'weak';
439 }
440 function validateEmail(email) {
441 var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
442 return re.test(email);
443 }
444 function loadScript(jsUrl, cssUrl, callback){
445 // adding the script tag to the head
446 var head = document.getElementsByTagName('head')[0];
447 var script = document.createElement('script');
448 script.type = 'text/javascript';
449 script.src = jsUrl;
450
451 if(cssUrl != ''){
452 var css = document.createElement('link');
453 css.href = cssUrl;
454 css.rel = "stylesheet";
455 css.type = "text/css";
456 head.appendChild(css);
457 }
458
459 // then bind the event to the callback function
460 // there are several events for cross browser compatibility
461 if(script.onreadystatechange != undefined){ script.onreadystatechange = callback; }
462 if(script.onload != undefined){ script.onload = callback; }
463
464 // fire the loading
465 head.appendChild(script);
466 }
467 /**
468 * options object. The following members can be provided:
469 * url: iframe url to load
470 * message: instead of a url to open, you could pass a message. HTML tags allowed.
471 * id: id attribute of modal window. auto-generated if not provided
472 * title: optional modal window title
473 * size: 'default', 'full'
474 * close: optional function to execute on closing the modal
475 * footer: optional array of objects describing the buttons to display in the footer.
476 * Each button object can have the following members:
477 * label: string, label of button
478 * bs_class: string, button bootstrap class. Can be 'primary', 'default', 'success', 'warning' or 'danger'
479 * click: function to execute on clicking the button. If the button closes the modal, this
480 * function is executed before the close handler
481 * causes_closing: boolean, default is true.
482 */
483 function modal_window(options){
484 return jQuery('body').agModal(options).agModal('show').attr('id');
485 }
486
487 function random_string(string_length){
488 var text = "";
489 var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
490
491 for(var i = 0; i < string_length; i++)
492 text += possible.charAt(Math.floor(Math.random() * possible.length));
493
494 return text;
495 }
496
497 /**
498 * @return array of IDs (PK values) of selected records in TV (records that the user checked)
499 */
500 function get_selected_records_ids(){
501 return jQuery('.record_selector:checked').map(function(){ /* */ return jQuery(this).val() }).get();
502 }
503
504 function print_multiple_dv_tvdv(t, ids){
505 document.myform.NoDV.value=1;
506 document.myform.PrintDV.value=1;
507 document.myform.SelectedID.value = '';
508 document.myform.submit();
509 return true;
510 }
511
512 function print_multiple_dv_sdv(t, ids){
513 document.myform.NoDV.value=1;
514 document.myform.PrintDV.value=1;
515 document.myform.writeAttribute('novalidate', 'novalidate');
516 document.myform.submit();
517 return true;
518 }
519
520 function mass_delete(t, ids){
521 if(ids == undefined) return;
522 if(!ids.length) return;
523
524 var confirm_message = '<div class="alert alert-danger">' +
525 '<i class="glyphicon glyphicon-warning-sign"></i> ' +
526 '<?php echo addslashes($Translation['<n> records will be deleted. Are you sure you want to do this?']); ?>' +
527 '</div>';
528 var confirm_title = '<?php echo addslashes($Translation['Confirm deleting multiple records']); ?>';
529 var label_yes = '<?php echo addslashes($Translation['Yes, delete them!']); ?>';
530 var label_no = '<?php echo addslashes($Translation['No, keep them.']); ?>';
531 var progress = '<?php echo addslashes($Translation['Deleting record <i> of <n>']); ?>';
532 var continue_delete = true;
533
534 // request confirmation of mass delete operation
535 modal_window({
536 message: confirm_message.replace(/\<n\>/, ids.length),
537 title: confirm_title,
538 footer: [ /* shows a 'yes' and a 'no' buttons .. handler for each follows ... */
539 {
540 label: '<i class="glyphicon glyphicon-trash"></i> ' + label_yes,
541 bs_class: 'danger',
542 // on confirming, start delete operations
543 click: function(){
544
545 // show delete progress, allowing user to abort operations by closing the window or clicking cancel
546 var progress_window = modal_window({
547 title: '<?php echo addslashes($Translation['Delete progress']); ?>',
548 message: '' +
549 '<div class="progress">' +
550 '<div class="progress-bar progress-bar-warning" role="progressbar" style="width: 0;"></div>' +
551 '</div>' +
552 '<button type="button" class="btn btn-default details_toggle" onclick="' +
553 'jQuery(this).children(\'.glyphicon\').toggleClass(\'glyphicon-chevron-right glyphicon-chevron-down\'); ' +
554 'jQuery(\'.well.details_list\').toggleClass(\'hidden\');'
555 + '">' +
556 '<i class="glyphicon glyphicon-chevron-right"></i> ' +
557 '<?php echo addslashes($Translation['Show/hide details']); ?>' +
558 '</button>' +
559 '<div class="well well-sm details_list hidden"><ol></ol></div>',
560 close: function(){
561 // stop deleting further records ...
562 continue_delete = false;
563 },
564 footer: [
565 {
566 label: '<i class="glyphicon glyphicon-remove"></i> <?php echo addslashes($Translation['Cancel']); ?>',
567 bs_class: 'warning'
568 }
569 ]
570 });
571
572 // begin deleting records, one by one
573 progress = progress.replace(/\<n\>/, ids.length);
574 var delete_record = function(itrn){
575 if(!continue_delete) return;
576 jQuery.ajax(t + '_view.php', {
577 type: 'POST',
578 data: { delete_x: 1, SelectedID: ids[itrn] },
579 success: function(resp){
580 if(resp == 'OK'){
581 jQuery(".well.details_list ol").append('<li class="text-success"><?php echo addslashes($Translation['The record has been deleted successfully']); ?></li>');
582 jQuery('#record_selector_' + ids[itrn]).prop('checked', false).parent().parent().fadeOut(1500);
583 jQuery('#select_all_records').prop('checked', false);
584 }else{
585 jQuery(".well.details_list ol").append('<li class="text-danger">' + resp + '</li>');
586 }
587 },
588 error: function(){
589 jQuery(".well.details_list ol").append('<li class="text-warning"><?php echo addslashes($Translation['Connection error']); ?></li>');
590 },
591 complete: function(){
592 jQuery('#' + progress_window + ' .progress-bar').attr('style', 'width: ' + (Math.round((itrn + 1) / ids.length * 100)) + '%;').html(progress.replace(/\<i\>/, (itrn + 1)));
593 if(itrn < (ids.length - 1)){
594 delete_record(itrn + 1);
595 }else{
596 if(jQuery('.well.details_list li.text-danger, .well.details_list li.text-warning').length){
597 jQuery('button.details_toggle').removeClass('btn-default').addClass('btn-warning').click();
598 jQuery('.btn-warning[id^=' + progress_window + '_footer_button_]')
599 .toggleClass('btn-warning btn-default')
600 .html('<?php echo addslashes($Translation['ok']); ?>');
601 }else{
602 setTimeout(function(){ /* */ jQuery('#' + progress_window).agModal('hide'); }, 500);
603 }
604 }
605 }
606 });
607 }
608
609 delete_record(0);
610 }
611 },
612 {
613 label: '<i class="glyphicon glyphicon-ok"></i> ' + label_no,
614 bs_class: 'success'
615 }
616 ]
617 });
618 }
619
620 function mass_change_owner(t, ids){
621 if(ids == undefined) return;
622 if(!ids.length) return;
623
624 var update_form = '<?php echo addslashes($Translation['Change owner of <n> selected records to']); ?> ' +
625 '<span id="new_owner_for_selected_records"></span><input type="hidden" name="new_owner_for_selected_records" value="">';
626 var confirm_title = '<?php echo addslashes($Translation['Change owner']); ?>';
627 var label_yes = '<?php echo addslashes($Translation['Continue']); ?>';
628 var label_no = '<?php echo addslashes($Translation['Cancel']); ?>';
629 var progress = '<?php echo addslashes($Translation['Updating record <i> of <n>']); ?>';
630 var continue_updating = true;
631
632 // request confirmation of mass update operation
633 modal_window({
634 message: update_form.replace(/\<n\>/, ids.length),
635 title: confirm_title,
636 footer: [ /* shows a 'continue' and a 'cancel' buttons .. handler for each follows ... */
637 {
638 label: '<i class="glyphicon glyphicon-ok"></i> ' + label_yes,
639 bs_class: 'success',
640 // on confirming, start update operations
641 click: function(){
642 var memberID = jQuery('input[name=new_owner_for_selected_records]').eq(0).val();
643 if(!memberID.length) return;
644
645 // show update progress, allowing user to abort operations by closing the window or clicking cancel
646 var progress_window = modal_window({
647 title: '<?php echo addslashes($Translation['Update progress']); ?>',
648 message: '' +
649 '<div class="progress">' +
650 '<div class="progress-bar progress-bar-success" role="progressbar" style="width: 0;"></div>' +
651 '</div>' +
652 '<button type="button" class="btn btn-default details_toggle" onclick="' +
653 'jQuery(this).children(\'.glyphicon\').toggleClass(\'glyphicon-chevron-right glyphicon-chevron-down\'); ' +
654 'jQuery(\'.well.details_list\').toggleClass(\'hidden\');'
655 + '">' +
656 '<i class="glyphicon glyphicon-chevron-right"></i> ' +
657 '<?php echo addslashes($Translation['Show/hide details']); ?>' +
658 '</button>' +
659 '<div class="well well-sm details_list hidden"><ol></ol></div>',
660 close: function(){
661 // stop updating further records ...
662 continue_updating = false;
663 },
664 footer: [
665 {
666 label: '<i class="glyphicon glyphicon-remove"></i> <?php echo addslashes($Translation['Cancel']); ?>',
667 bs_class: 'warning'
668 }
669 ]
670 });
671
672 // begin updating records, one by one
673 progress = progress.replace(/\<n\>/, ids.length);
674 var update_record = function(itrn){
675 if(!continue_updating) return;
676 jQuery.ajax('admin/pageEditOwnership.php', {
677 type: 'POST',
678 data: {
679 pkValue: ids[itrn],
680 t: t,
681 memberID: memberID,
682 saveChanges: 'Save changes'
683 },
684 success: function(resp){
685 if(resp == 'OK'){
686 jQuery(".well.details_list ol").append('<li class="text-success"><?php echo addslashes($Translation['record updated']); ?></li>');
687 jQuery('#record_selector_' + ids[itrn]).prop('checked', false);
688 jQuery('#select_all_records').prop('checked', false);
689 }else{
690 jQuery(".well.details_list ol").append('<li class="text-danger">' + resp + '</li>');
691 }
692 },
693 error: function(){
694 jQuery(".well.details_list ol").append('<li class="text-warning"><?php echo addslashes($Translation['Connection error']); ?></li>');
695 },
696 complete: function(){
697 jQuery('#' + progress_window + ' .progress-bar').attr('style', 'width: ' + (Math.round((itrn + 1) / ids.length * 100)) + '%;').html(progress.replace(/\<i\>/, (itrn + 1)));
698 if(itrn < (ids.length - 1)){
699 update_record(itrn + 1);
700 }else{
701 if(jQuery('.well.details_list li.text-danger, .well.details_list li.text-warning').length){
702 jQuery('button.details_toggle').removeClass('btn-default').addClass('btn-warning').click();
703 jQuery('.btn-warning[id^=' + progress_window + '_footer_button_]')
704 .toggleClass('btn-warning btn-default')
705 .html('<?php echo addslashes($Translation['ok']); ?>');
706 }else{
707 jQuery('button.btn-warning[id^=' + progress_window + '_footer_button_]')
708 .toggleClass('btn-warning btn-success')
709 .html('<i class="glyphicon glyphicon-ok"></i> <?php echo addslashes($Translation['ok']); ?>');
710 }
711 }
712 }
713 });
714 }
715
716 update_record(0);
717 }
718 },
719 {
720 label: '<i class="glyphicon glyphicon-remove"></i> ' + label_no,
721 bs_class: 'warning'
722 }
723 ]
724 });
725
726 /* show drop down of users */
727 var populate_new_owner_dropdown = function(){
728
729 jQuery('[id=new_owner_for_selected_records]').select2({
730 width: '100%',
731 formatNoMatches: function(term){ /* */ return '<?php echo addslashes($Translation['No matches found!']); ?>'; },
732 minimumResultsForSearch: 10,
733 loadMorePadding: 200,
734 escapeMarkup: function(m){ /* */ return m; },
735 ajax: {
736 url: 'admin/getUsers.php',
737 dataType: 'json',
738 cache: true,
739 data: function(term, page){ /* */ return { s: term, p: page, t: t }; },
740 results: function(resp, page){ /* */ return resp; }
741 }
742 }).on('change', function(e){
743 jQuery('[name="new_owner_for_selected_records"]').val(e.added.id);
744 });
745
746 }
747
748 populate_new_owner_dropdown();
749 }
750
751 function add_more_actions_link(){
752 window.open('https://bigprof.com/appgini/help/advanced-topics/hooks/multiple-record-batch-actions?r=appgini-action-menu');
753 }
754
755 /* detect current screen size (xs, sm, md or lg) */
756 function screen_size(sz){
757 if(!$j('.device-xs').length){
758 $j('body').append(
759 '<div class="device-xs visible-xs"></div>' +
760 '<div class="device-sm visible-sm"></div>' +
761 '<div class="device-md visible-md"></div>' +
762 '<div class="device-lg visible-lg"></div>'
763 );
764 }
765 return $j('.device-' + sz).is(':visible');
766 }
767
768 /* enable floating of action buttons in DV so they are visible on vertical scrolling */
769 function enable_dvab_floating(){
770 /* already run? */
771 if(window.enable_dvab_floating_run != undefined) return;
772
773 /* scroll action buttons of DV on scrolling DV */
774 $j(window).scroll(function(){
775 if(!screen_size('md') && !screen_size('lg')) return;
776 if(!$j('.detail_view').length) return;
777
778 /* get vscroll amount, DV form height, button toolbar height and position */
779 var vscroll = $j(window).scrollTop();
780 var dv_height = $j('[id$="_dv_form"]').eq(0).height();
781 var bt_height = $j('.detail_view .btn-toolbar').height();
782 var form_top = $j('.detail_view .form-group').eq(0).offset().top;
783 var bt_top_max = dv_height - bt_height - 10;
784
785 if(vscroll > form_top){
786 var tm = parseInt(vscroll - form_top) + 60;
787 if(tm > bt_top_max) tm = bt_top_max;
788
789 $j('.detail_view .btn-toolbar').css({ 'margin-top': tm + 'px' });
790 }else{
791 $j('.detail_view .btn-toolbar').css({ 'margin-top': 0 });
792 }
793 });
794 window.enable_dvab_floating_run = true;
795 }
796
797 /* check if a given field's value is unique and reflect this in the DV form */
798 function enforce_uniqueness(table, field){
799 $j('#' + field).on('change', function(){
800 /* check uniqueness of field */
801 var data = {
802 t: table,
803 f: field,
804 value: $j('#' + field).val()
805 };
806
807 if($j('[name=SelectedID]').val().length) data.id = $j('[name=SelectedID]').val();
808
809 $j.ajax({
810 url: 'ajax_check_unique.php',
811 data: data,
812 complete: function(resp){
813 if(resp.responseJSON.result == 'ok'){
814 $j('#' + field + '-uniqueness-note').hide();
815 $j('#' + field).parents('.form-group').removeClass('has-error');
816 }else{
817 $j('#' + field + '-uniqueness-note').show();
818 $j('#' + field).parents('.form-group').addClass('has-error');
819 $j('#' + field).focus();
820 setTimeout(function(){ /* */ $j('#update, #insert').prop('disabled', true); }, 500);
821 }
822 }
823 })
824 });
825 }
826
827 /* persist expanded/collapsed chidren in DVP */
828 function persist_expanded_child(id){
829 var expand_these = Cookies.getJSON('Jisort.dvp_expand');
830 if(expand_these == undefined) expand_these = [];
831
832 if($j('[id=' + id + ']').hasClass('active')){
833 if(expand_these.indexOf(id) < 0){
834 // expanded button and not persisting in cookie? save it!
835 expand_these.push(id);
836 Cookies.set('Jisort.dvp_expand', expand_these, { expires: 30 });
837 }
838 }else{
839 if(expand_these.indexOf(id) >= 0){
840 // collapsed button and persisting in cookie? remove it!
841 expand_these.splice(expand_these.indexOf(id), 1);
842 Cookies.set('Jisort.dvp_expand', expand_these, { expires: 30 });
843 }
844 }
845 }
846
847 /* apply expanded/collapsed status to children in DVP */
848 function apply_persisting_children(){
849 var expand_these = Cookies.getJSON('Jisort.dvp_expand');
850 if(expand_these == undefined) return;
851
852 expand_these.each(function(id){
853 $j('[id=' + id + ']:not(.active)').click();
854 });
855 }
856
857 function select2_max_width_decrement(){
858 return ($j('div.container').eq(0).hasClass('theme-compact') ? 99 : 109);
859 }
860
861 /**
862 * @brief AppGini.TVScroll().more() to scroll one column more.
863 * AppGini.TVScroll().less() to scroll one column less.
864 */
865 AppGini.TVScroll = function(){
866
867 /**
868 * @brief Calculates the width of the first n columns of the TV table
869 *
870 * @param [in] n how many columns to calculate the width for
871 * @return Return total width of given n columns, or 0 if n < 1 or invalid
872 */
873 var _TVColsWidth = function(n){
874 if(isNaN(n)) return 0;
875 if(n < 1) return 0;
876
877 var tw = 0, cc;
878 for(var i = 0; i < n; i++){
879 cc = $j('.table_view .table th:visible').eq(i);
880 if(!cc.length) break;
881 tw += cc.outerWidth();
882 }
883
884 return tw;
885 };
886
887 /**
888 * @brief show/hide tv-scroll buttons based on whether TV is horizontally scrollable or not
889 * @details should be called once on document load before hiding TV columns (by calling less())
890 */
891 var toggle_tv_scroll_tools = function(){
892 var tr = $j('.table_view .table-responsive'),
893 vpw = tr.width(), // viewport width
894 tfw = tr.find('.table').width(); // full width of the table
895
896 if(vpw >= tfw) $j('.tv-scroll').parents('.btn-group').hide();
897 else $j('.tv-scroll').parents('.btn-group').show();
898 }
899
900 /**
901 * @brief Prepares variables for use by less & more
902 */
903 var _TVScrollSetup = function(){
904 if(AppGini._TVColsScrolled === undefined) AppGini._TVColsScrolled = 0;
905 AppGini._TVColsCount = $j('.table_view .table th:visible').length;
906
907 /* type of scrolling, https://github.com/othree/jquery.rtl-scroll-type */
908 /*
909 How to interpret AppGini._ScrollType?
910 {LTR | RTL}:{scrollLeft val for left position}:{scrollLeft val for right position}:{initial scrollLeft val}
911 */
912 if(AppGini._ScrollType === undefined){
913 /* all browsers behave the same on LTR */
914 AppGini._ScrollType = 'LTR:0:100:0';
915
916 if($j('.container').hasClass('theme-rtl')){
917 var definer = $j('<div dir="rtl" style="font-size: 14px; width: 4px; height: 1px; position: absolute; top: -1000px; overflow: scroll">ABCD</div>').appendTo('body')[0];
918
919 AppGini._ScrollType = 'RTL:100:0:0'; // IE
920 if(definer.scrollLeft > 0){
921 AppGini._ScrollType = 'RTL:0:100:70'; // WebKit
922 }else{
923 definer.scrollLeft = 1;
924 if(definer.scrollLeft === 0) AppGini._ScrollType = 'RTL:-100:0:0'; // Firefox/Opera
925 }
926 }
927
928 /* show/hide #tv-scroll buttons based on TV scroll state */
929 $j(window).resize(toggle_tv_scroll_tools);
930 toggle_tv_scroll_tools();
931 }
932 };
933
934 /**
935 * @brief Resets all scrolling and setup values.
936 * @details Useful after hiding/showing columns to re-setup TV scrolling
937 */
938 var reset = function(){
939 if(AppGini._ScrollType === undefined) return; // nothing to reset!
940 AppGini._TVColsScrolled = undefined;
941
942 var tr = $j('.table_view .table-responsive');
943 switch(AppGini._ScrollType){
944 case 'RTL:100:0:0':
945 case 'RTL:0:100:0':
946 case 'RTL:-100:0:0':
947 tr.scrollLeft(0);
948 break;
949 case 'RTL:0:100:70':
950 var vpw = tr.width(), // viewport width
951 tfw = tr.find('.table').width(); // full width of the table
952 tr.scrollLeft(tfw - vpw + 10);
953 break;
954 }
955
956 _TVScrollSetup();
957 };
958
959 var _TVScroll = function(){
960 var scroll = 0,
961 tr = $j('.table_view .table-responsive'),
962 cw = _TVColsWidth(AppGini._TVColsScrolled); // width of columns to scroll to
963
964 switch(AppGini._ScrollType){
965 case 'RTL:100:0:0':
966 case 'LTR:0:100:0':
967 scroll = cw - 1;
968 break;
969 case 'RTL:-100:0:0':
970 scroll = -1 * cw + 1;
971 break;
972 case 'RTL:0:100:70':
973 var vpw = tr.width(), // viewport width
974 tfw = tr.find('.table').width(); // full width of the table
975 scroll = tfw - vpw - cw + 1;
976 break;
977 }
978
979 tr.scrollLeft(scroll);
980 };
981
982 /**
983 * @brief Scroll the TV table 1 column more
984 */
985 var more = function(){
986 if(AppGini._TVColsScrolled >= AppGini._TVColsCount) return;
987 AppGini._TVColsScrolled++;
988 _TVScroll();
989 };
990
991 /**
992 * @brief Scroll the TV table 1 column less
993 */
994 var less = function(){
995 if(AppGini._TVColsScrolled <= 0) return;
996 AppGini._TVColsScrolled--;
997 _TVScroll();
998 };
999
1000 _TVScrollSetup();
1001
1002 return { more: more, less: less, reset: reset };
1003
1004 };
1005
1006 (function($j){
1007 /*
1008 apply a modal or an in-page modal to an element,
1009 or access modal methods/events if it's already 'modal'ed
1010
1011 Expected usage:
1012 1. $j('any_selector').agModal({ new modal options .. })
1013 2. $j('#modal_id').agModal('command')
1014 3. $j('#modal_id').on('event.bs.modal', event_handler)
1015
1016 case 1: the selector doesn't matter ... the modal will be created and attached
1017 to the body element .. to retrieve the modal id if not specified in options:
1018 var modal_id = $j('any_selector').agModal({ new modal options .. }).attr('id');
1019
1020 case 2: the selector must be the modal element .. if it's a standard BS modal,
1021 command will be passed as is to .modal() and the return value returned.
1022 if it's an in-page modal, command will be emulated and the modal element
1023 returned.
1024
1025 case 3: Bootstrap modal events.
1026 */
1027 $j.fn.agModal = function(options){
1028 var theModal = this,
1029 open = function(){
1030 return theModal.trigger('show.bs.modal').removeClass('hide').trigger('shown.bs.modal');
1031 },
1032 close = function(){
1033 return theModal.trigger('hide.bs.modal').addClass('hide').trigger('hidden.bs.modal');
1034 };
1035
1036 if(typeof(options) == 'string'){
1037 if(theModal.hasClass('modal')) return theModal.modal(options);
1038 if(!theModal.hasClass('inpage-modal')) return theModal;
1039
1040 /* emulate .modal(command) for the in-page modal */
1041 switch(options){
1042 case 'show':
1043 open();
1044 break;
1045 case 'hide':
1046 close();
1047 break;
1048 }
1049
1050 return theModal;
1051 }
1052
1053 var op = $j.extend({
1054 /* default options */
1055 id: random_string(20),
1056 footer: [],
1057 extras: {},
1058 size: 'default',
1059 forceIPM: false
1060 }, options);
1061
1062 if(op.url == undefined && op.message == undefined){
1063 console.error('Missing message/url in call to AppGini.modal().');
1064 return theModal;
1065 }
1066
1067 var iOS = /(iPad|iPhone|iPod)/g.test(navigator.userAgent), /* true for iOS devices */
1068 auto_id = (options.id === undefined), /* true if modal id is auto-generated */
1069
1070 _resize = function(id){
1071 var mod = $j('#' + id);
1072 if(!mod.length) return;
1073
1074 var ipm = (mod.hasClass('inpage-modal') ? '.inpage-modal-' : '.modal-');
1075
1076 var wh = $j(window).height(),
1077 mtm = mod.find(ipm + 'dialog').css('margin-top'),
1078 mhfoh = mod.find(ipm + 'header').outerHeight() + mod.find(ipm + 'footer').outerHeight();
1079
1080 mod.find(ipm + 'dialog').css({
1081 margin: mtm,
1082 width: 'calc(100% - 2 * ' + mtm + ')'
1083 });
1084
1085 mod.find(ipm + 'body').css({
1086 height: 'calc(' + wh + 'px - ' + mhfoh + 'px - 2 * ' + mtm + ' - 6px)'
1087 });
1088 },
1089
1090 _bsModal = function(){
1091 /* build the html of footer buttons into footer_buttons variable */
1092 var footer_buttons = '';
1093 for(i = 0; i < op.footer.length; i++){
1094 if(typeof(op.footer[i].label) != 'string') continue;
1095
1096 op.footer[i] = $j.extend(
1097 /* defaults */
1098 {
1099 causes_closing: true,
1100 bs_class: 'default'
1101 },
1102 op.footer[i],
1103 /* enforce the following values */
1104 { id: op.id + '_footer_button_' + random_string(10) }
1105 );
1106
1107 footer_buttons += '<button ' +
1108 'type="button" ' +
1109 'class="btn btn-' + op.footer[i].bs_class + '" ' +
1110 (op.footer[i].causes_closing ? 'data-dismiss="modal" ' : '') +
1111 'id="' + op.footer[i].id + '" ' +
1112 '>' + op.footer[i].label +
1113 '</button>';
1114 }
1115
1116 var mod = $j(
1117 '<div class="modal fade" tabindex="-1" role="dialog" id="' + op.id + '">' +
1118 '<div class="modal-dialog" role="document">' +
1119 '<div class="modal-content">' +
1120 ( op.title != undefined ?
1121 '<div class="modal-header">' +
1122 '<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>' +
1123 '<h4 class="modal-title" style="width: 90%;">' + op.title + '</h4>' +
1124 '</div>'
1125 : ''
1126 ) +
1127 '<div class="modal-body">' +
1128 ( op.url != undefined ?
1129 '<iframe ' +
1130 'width="100%" height="100%" ' +
1131 'style="display: block; overflow: scroll !important; -webkit-overflow-scrolling: touch !important;" ' +
1132 'sandbox="allow-modals allow-forms allow-scripts allow-same-origin allow-popups" ' +
1133 'src="' + op.url + '">' +
1134 '</iframe>'
1135 : op.message
1136 ) +
1137 '</div>' +
1138 '<div class="modal-footer">' + footer_buttons + '</div>' +
1139 '</div>' +
1140 '</div>' +
1141 '</div>'
1142 );
1143
1144 if(op.url != undefined){
1145 mod.find('.modal-body').css('padding', '0');
1146 }
1147
1148 return mod;
1149 },
1150
1151 _ipModal = function(){
1152 /* prepare footer buttons, if any */
1153 var footer_buttons = '', closer_class = '';
1154 for(i = 0; i < op.footer.length; i++){
1155 if(typeof(op.footer[i].label) != 'string') continue;
1156
1157 if(op.footer[i].causes_closing !== false){ op.footer[i].causes_closing = true; }
1158 op.footer[i].bs_class = op.footer[i].bs_class || 'default';
1159 op.footer[i].id = op.id + '_footer_button_' + random_string(10);
1160
1161 closer_class = (op.footer[i].causes_closing ? ' closes-inpage-modal' : '');
1162
1163 footer_buttons += '<button type="button" ' +
1164 'class="hspacer-lg vspacer-lg btn btn-' + op.footer[i].bs_class + closer_class + '" ' +
1165 'id="' + op.footer[i].id + '" ' +
1166 '>' + op.footer[i].label + '</button>';
1167 }
1168
1169 var imc = $j(
1170 '<div id="' + op.id + '" ' +
1171 'class="inpage-modal hide ' + $j('.container').eq(0).attr('class') + '" ' +
1172 'style="' +
1173 'padding-left: 0; padding-right: 0;' +
1174 'width: 100% !important;' +
1175 '">' +
1176 '<div ' +
1177 'class="inpage-modal-dialog" ' +
1178 'style="' +
1179 'box-shadow: 0 0 61px 15px #666;' +
1180 'margin: 10px !important;' +
1181 'border: solid 1px;' +
1182 'border-radius: 5px;' +
1183 '">' +
1184 '<div class="inpage-modal-content">' +
1185 ( op.title != undefined ?
1186 '<div class="inpage-modal-header" style="border-bottom: solid 1px;">' +
1187 '<div class="row" style="margin: 0;">' +
1188 '<div class="col-xs-10 col-sm-11 inpage-modal-title">' +
1189 '<div class="h4">' + op.title + '</div>' +
1190 '</div>' +
1191 '<div class="col-xs-2 col-sm-1 closes-inpage-modal text-center inpage-modal-dismiss" style="cursor: pointer;">' +
1192 '<h4 class="glyphicon glyphicon-remove"></h4>' +
1193 '</div>' +
1194 '</div>' +
1195 '</div>'
1196 : ''
1197 ) +
1198
1199 ( footer_buttons.length ?
1200 '<div class="inpage-modal-footer text-right flip" style="border-bottom: solid 1px;">' + footer_buttons + '</div>'
1201 : ''
1202 ) +
1203
1204 '<div class="inpage-modal-body">' +
1205 ( op.url != undefined ?
1206 '<iframe ' +
1207 'width="100%" height="100%" ' +
1208 'style="display: block; overflow: scroll !important; -webkit-overflow-scrolling: touch !important;" ' +
1209 'sandbox="allow-modals allow-forms allow-scripts allow-same-origin allow-popups" ' +
1210 'src="' + op.url + '">' +
1211 '</iframe>'
1212 : op.message
1213 ) +
1214 '</div>' +
1215 '</div>' +
1216 '</div>' +
1217 '</div>'
1218 );
1219
1220 /* hover effect for dismiss button + close modal if a closer clicked */
1221 imc.on('mouseover', '.inpage-modal-dismiss', function(){
1222 $j(this).addClass('text-danger bg-danger');
1223 }).on('mouseout', '.inpage-modal-dismiss', function(){
1224 $j(this).removeClass('text-danger bg-danger');
1225 }).on('click', '.closes-inpage-modal', close);
1226
1227 imc.find('.inpage-modal-title').css({
1228 overflow: 'auto',
1229 'white-space': 'nowrap'
1230 });
1231
1232 return imc;
1233 };
1234
1235 /* if modal exists, remove it first */
1236 $j('#' + op.id).remove();
1237
1238 theModal = ((iOS || op.forceIPM) && op.size == 'full' ? _ipModal() : _bsModal());
1239
1240 theModal.appendTo('body');
1241
1242 /* bind footer buttons click handlers */
1243 for(i = 0; i < op.footer.length; i++){
1244 if(typeof(op.footer[i].click) == 'function'){
1245 $j('#' + op.footer[i].id).click(op.footer[i].click);
1246 }
1247 }
1248
1249 theModal
1250 .on('show.bs.modal', function(){
1251 if(op.size != 'full') return;
1252
1253 /* hide main page to avoid all scrolling/panning hell on touch screens! */
1254 $j('.container').eq(0).hide();
1255 })
1256 .on('shown.bs.modal', function(){
1257 if(op.size != 'full') return;
1258
1259 var id = op.id, rsz = _resize;
1260 rsz(id);
1261 $j(window).resize(function(){ /* */ rsz(id); });
1262 })
1263 //.agModal('show')
1264 .on('hidden.bs.modal', function(){
1265 /* display main page again */
1266 if(op.size == 'full') $j('.container').eq(0).show();
1267
1268 if(typeof(op.close) == 'function'){
1269 op.close();
1270 }
1271
1272 if(!auto_id) return;
1273
1274 /* if id is automatic, remove modal after 1 minute from DOM */
1275 var id = op.id;
1276 var auto_remove = setInterval(function(){
1277 if($j('#' + id).is(':visible')) return; // don't remove if visible
1278 $j('#' + id).remove();
1279 clearInterval(auto_remove);
1280 }, 60000);
1281 });
1282
1283 return theModal;
1284 };
1285 })(jQuery);
1286
1287 /**
1288 * @brief Used in pages loaded inside modals (e.g. those with Embedded=1) to close the containing modal.
1289 */
1290 AppGini.closeParentModal = function(){
1291 var pm = window.parent.jQuery(".modal:visible");
1292 if(!pm.length){
1293 pm = window.parent.jQuery(".inpage-modal:visible");
1294 }
1295
1296 if(pm.length) pm.agModal('hide');
1297 return;
1298 }
1299
1300 /**
1301 * @return boolean indicating whether a modal is currently open or not
1302 */
1303 AppGini.modalOpen = function(){ /* */
1304 return jQuery('.modal-dialog:visible').length > 0 || jQuery('.inpage-modal-dialog:visible').length > 0;
1305 };
1306
1307
1308 /**
1309 * @return true for mobile devices, false otherwise
1310 * @details https://stackoverflow.com/a/11381730/1945185
1311 */
1312 AppGini.mobileDevice = function(){ /* */
1313 var check = false;
1314 (function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4))) check = true;})(navigator.userAgent||navigator.vendor||window.opera);
1315 return check;
1316 };
1317
1318 AppGini.datetimeFormat = function(datetime){ /* */
1319 if(undefined == datetime) datetime = 'd';
1320
1321 var dateFormat = 'MM/DD/YYYY';
1322 var timeFormat = 'hh:mm:ss A';
1323
1324 if(datetime.match(/(dt|td)/i)) return dateFormat + ' ' + timeFormat;
1325 if(datetime.match(/t/i)) return timeFormat;
1326 return dateFormat;
1327 };
1328
1329 AppGini.hideViewParentLinks = function() {
1330 /* find and hide parent links if field label has data 'parent_link' set to 'view_parent_hidden' */
1331 $j('label[data-parent_link=view_parent_hidden]').each(function(){
1332 $j(this).parents('.form-group').find('.view_parent').hide();
1333 });
1334 };
2 if(!defined('datalist_db_encoding')) define('datalist_db_encoding', 'UTF-8');
3 if(function_exists('date_default_timezone_set')) @date_default_timezone_set('America/New_York');
4
5 /* force caching */
6 $last_modified = filemtime(__FILE__);
7 $last_modified_gmt = gmdate('D, d M Y H:i:s', $last_modified) . ' GMT';
8 $headers = (function_exists('getallheaders') ? getallheaders() : $_SERVER);
9 if(isset($headers['If-Modified-Since']) && (strtotime($headers['If-Modified-Since']) == $last_modified)){
10 @header("Last-Modified: {$last_modified_gmt}", true, 304);
11 @header("Cache-Control: public, max-age=240", true);
12 exit;
13 }
14
15 @header("Last-Modified: {$last_modified_gmt}", true, 200);
16 @header("Cache-Control: public, max-age=240", true);
17 @header('Content-Type: text/javascript; charset=' . datalist_db_encoding);
18 $currDir = dirname(__FILE__);
19 include("{$currDir}/defaultLang.php");
20 include("{$currDir}/language.php");
21 ?>
22 var AppGini = AppGini || {};
23 AppGini.ajaxCache = function(){
24 var _tests = [];
25
26 /*
27 An array of functions that receive a parameterless url and a parameters object,
28 makes a test,
29 and if test passes, executes something and/or
30 returns a non-false value if test passes,
31 or false if test failed (useful to tell if tests should continue or not)
32 */
33 var addCheck = function(check){ /* */
34 if(typeof(check) == 'function'){
35 _tests.push(check);
36 }
37 };
38
39 var _jqAjaxData = function(opt){ /* */
40 var opt = opt || {};
41 var url = opt.url || '';
42 var data = opt.data || {};
43
44 var params = url.match(/\?(.*)$/);
45 var param = (params !== null ? params[1] : '');
46
47 var sPageURL = decodeURIComponent(param),
48 sURLVariables = sPageURL.split('&'),
49 sParameter,
50 i;
51
52 for(i = 0; i < sURLVariables.length; i++){
53 sParameter = sURLVariables[i].split('=');
54 if(sParameter[0] == '') continue;
55 data[sParameter[0]] = sParameter[1] || '';
56 }
57
58 return data;
59 };
60
61 var start = function(){ /* */
62 if(!_tests.length) return; // no need to monitor ajax requests since no checks were defined
63 var reqTests = _tests;
64 $j.ajaxPrefilter(function(options, originalOptions, jqXHR){
65 var success = originalOptions.success || $j.noop,
66 data = _jqAjaxData(originalOptions),
67 oUrl = originalOptions.url || '',
68 url = oUrl.match(/\?/) ? oUrl.match(/(.*)\?/)[1] : oUrl;
69
70 options.beforeSend = function(){ /* */
71 var req, cached = false, resp;
72
73 for(var i = 0; i < reqTests.length; i++){
74 resp = reqTests[i](url, data);
75 if(resp === false) continue;
76
77 success(resp);
78 return false;
79 }
80
81 return true;
82 }
83 });
84 };
85
86 return {
87 addCheck: addCheck,
88 start: start
89 };
90 };
91
92 /* initials and fixes */
93 jQuery(function(){
94 AppGini.count_ajaxes_blocking_saving = 0;
95
96 /* add ":truncated" pseudo-class to detect elements with clipped text */
97 $j.expr[':'].truncated = function(obj){
98 var $this = $j(obj);
99 var $c = $this
100 .clone()
101 .css({ display: 'inline', width: 'auto', visibility: 'hidden', 'padding-right': 0 })
102 .css({ 'font-size': $this.css('font-size') })
103 .appendTo('body');
104
105 var e_width = $this.outerWidth();
106 var c_width = $c.outerWidth();
107 $c.remove();
108
109 return ( c_width > e_width );
110 };
111
112 var fix_lookup_width = function(field){
113 var s2 = $j('div.select2-container[id=s2id_' + field + '-container]');
114 if(!s2.length) return;
115
116 var s2new_width = 0, s2view_width = 0, s2parent_width = 0;
117
118 var s2new = s2.parent().find('.add_new_parent:visible');
119 var s2view = s2.parent().find('.view_parent:visible');
120 if(s2new.length) s2new_width = s2new.outerWidth(true);
121 if(s2view.length) s2view_width = s2view.outerWidth(true);
122 s2parent_width = s2.parent().innerWidth();
123
124 // console.log({ s2new_width: s2new_width, s2view_width: s2view_width, s2parent_width: s2parent_width });
125
126 s2.css({ width: '100%', 'max-width': (s2parent_width - s2new_width - s2view_width - 1) + 'px' });
127 }
128
129 $j(window).resize(function(){
130 var window_width = $j(window).width();
131 var max_width = $j('body').width() * 0.5;
132
133 $j('.select2-container:not(.option_list)').each(function(){
134 var field = $j(this).attr('id').replace(/^s2id_/, '').replace(/-container$/, '');
135 fix_lookup_width(field);
136 });
137
138 //fix_table_responsive_width();
139
140 var full_img_factor = 0.9; /* xs */
141 if(window_width >= 992) full_img_factor = 0.6; /* md, lg */
142 else if(window_width >= 768) full_img_factor = 0.9; /* sm */
143
144 $j('.detail_view .img-responsive').css({'max-width' : parseInt($j('.detail_view').width() * full_img_factor) + 'px'});
145
146 /* remove labels from truncated buttons, leaving only glyphicons */
147 $j('.btn.truncate:truncated').each(function(){
148 // hide text
149 var label = $j(this).html();
150 var mlabel = label.replace(/.*(<i.*?><\/i>).*/, '$1');
151 $j(this).html(mlabel);
152 });
153 });
154
155 setTimeout(function(){ /* */ $j(window).resize(); }, 1000);
156 setTimeout(function(){ /* */ $j(window).resize(); }, 3000);
157
158 /* don't allow saving detail view when there's an ajax request to a url that matches the following */
159 var ajax_blockers = new RegExp(/(ajax_combo\.php|_autofill\.php|ajax_check_unique\.php)/);
160 $j(document).ajaxSend(function(e, r, s){
161 if(s.url.match(ajax_blockers)){
162 AppGini.count_ajaxes_blocking_saving++;
163 $j('#update, #insert').prop('disabled', true);
164 }
165 });
166 $j(document).ajaxComplete(function(e, r, s){
167 if(s.url.match(ajax_blockers)){
168 AppGini.count_ajaxes_blocking_saving = Math.max(AppGini.count_ajaxes_blocking_saving - 1, 0);
169 if(AppGini.count_ajaxes_blocking_saving <= 0)
170 $j('#update, #insert').prop('disabled', false);
171 }
172 });
173
174 /* don't allow responsive images to initially exceed the smaller of their actual dimensions, or .6 container width */
175 jQuery('.detail_view .img-responsive').each(function(){
176 var pic_real_width, pic_real_height;
177 var img = jQuery(this);
178 jQuery('<img/>') // Make in memory copy of image to avoid css issues
179 .attr('src', img.attr('src'))
180 .load(function() {
181 pic_real_width = this.width;
182 pic_real_height = this.height;
183
184 if(pic_real_width > $j('.detail_view').width() * .6) pic_real_width = $j('.detail_view').width() * .6;
185 img.css({ "max-width": pic_real_width });
186 });
187 });
188
189 jQuery('.table-responsive .img-responsive').each(function(){
190 var pic_real_width, pic_real_height;
191 var img = jQuery(this);
192 jQuery('<img/>') // Make in memory copy of image to avoid css issues
193 .attr('src', img.attr('src'))
194 .load(function() {
195 pic_real_width = this.width;
196 pic_real_height = this.height;
197
198 if(pic_real_width > $j('.table-responsive').width() * .6) pic_real_width = $j('.table-responsive').width() * .6;
199 img.css({ "max-width": pic_real_width });
200 });
201 });
202
203 /* toggle TV action buttons based on selected records */
204 jQuery('.record_selector').click(function(){
205 var id = jQuery(this).val();
206 var checked = jQuery(this).prop('checked');
207 update_action_buttons();
208 });
209
210 /* select/deselect all records in TV */
211 jQuery('#select_all_records').click(function(){
212 jQuery('.record_selector').prop('checked', jQuery(this).prop('checked'));
213 update_action_buttons();
214 });
215
216 /* fix behavior of select2 in bootstrap modal. See: https://github.com/ivaynberg/select2/issues/1436 */
217 jQuery.fn.modal.Constructor.prototype.enforceFocus = function(){ /* */ };
218
219 /* remove empty navbar menus */
220 $j('nav li.dropdown').each(function(){
221 var num_items = $j(this).children('.dropdown-menu').children('li').length;
222 if(!num_items) $j(this).remove();
223 })
224
225 update_action_buttons();
226
227 /* remove empty images and links from TV, TVP */
228 $j('.table a[href="<?php echo $Translation['ImageFolder']; ?>"], .table img[src="<?php echo $Translation['ImageFolder']; ?>"]').remove();
229
230 /* remove empty email links from TV, TVP */
231 $j('a[href="mailto:"]').remove();
232
233 /* Disable action buttons when form is submitted to avoid user re-submission on slow connections */
234 $j('form').eq(0).submit(function(){
235 setTimeout(function(){
236 $j('#insert, #update, #delete, #deselect').prop('disabled', true);
237 }, 200); // delay purpose is to allow submitting the button values first then disable them.
238 });
239
240 /* fix links inside alerts */
241 $j('.alert a:not(.btn)').addClass('alert-link');
242 });
243
244 /* show/hide TV action buttons based on whether records are selected or not */
245 function update_action_buttons(){
246 if(jQuery('.record_selector:checked').length){
247 jQuery('.selected_records').removeClass('hidden');
248 jQuery('#select_all_records')
249 .prop('checked', (jQuery('.record_selector:checked').length == jQuery('.record_selector').length));
250 }else{
251 jQuery('.selected_records').addClass('hidden');
252 }
253 }
254
255 /* fix table-responsive behavior on Chrome */
256 function fix_table_responsive_width(){
257 var resp_width = jQuery('div.table-responsive').width();
258 var table_width;
259
260 if(resp_width){
261 jQuery('div.table-responsive table').width('100%');
262 table_width = jQuery('div.table-responsive table').width();
263 resp_width = jQuery('div.table-responsive').width();
264 if(resp_width == table_width){
265 jQuery('div.table-responsive table').width(resp_width - 1);
266 }
267 }
268 }
269
270 function schools_validateData(){
271 $j('.has-error').removeClass('has-error');
272 /* Field name can't be empty */
273 if($j('#name').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Name", close: function(){ /* */ $j('[name=name]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
274 return true;
275 }
276 function departments_validateData(){
277 $j('.has-error').removeClass('has-error');
278 /* Field name can't be empty */
279 if($j('#name').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Name", close: function(){ /* */ $j('[name=name]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
280 /* Field school can't be empty */
281 if($j('#school').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> School", close: function(){ /* */ $j('[name=school]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
282 return true;
283 }
284 function class_time_table_validateData(){
285 $j('.has-error').removeClass('has-error');
286 /* Field day can't be empty */
287 if($j('#day').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Day", close: function(){ /* */ $j('[name=day]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
288 /* Field time_start can't be empty */
289 if($j('#time_start').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Time Start", close: function(){ /* */ $j('[name=time_start]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
290 /* Field time_end can't be empty */
291 if($j('#time_end').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Time End", close: function(){ /* */ $j('[name=time_end]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
292 /* Field unit_code can't be empty */
293 if($j('#unit_code').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Unit code", close: function(){ /* */ $j('[name=unit_code]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
294 /* Field venue can't be empty */
295 if($j('#venue').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Venue", close: function(){ /* */ $j('[name=venue]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
296 /* Field school can't be empty */
297 if($j('#school').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> School", close: function(){ /* */ $j('[name=school]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
298 /* Field department can't be empty */
299 if($j('#department').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Department", close: function(){ /* */ $j('[name=department]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
300 /* Field year_of_study can't be empty */
301 if($j('#year_of_study').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Year of study", close: function(){ /* */ $j('[name=year_of_study]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
302 return true;
303 }
304 function exam_time_table_validateData(){
305 $j('.has-error').removeClass('has-error');
306 /* Date field date can't be empty */
307 if($j('#date-dd').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Date", close: function(){ /* */ $j('#date-dd').focus().parents('.form-group').addClass('has-error'); } }); return false; };
308 if($j('#date-mm').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Date", close: function(){ /* */ $j('#date-mm').focus().parents('.form-group').addClass('has-error'); } }); return false; };
309 if($j('#date').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Date", close: function(){ /* */ $j('#date').focus().parents('.form-group').addClass('has-error'); } }); return false; };
310 /* Field time_start can't be empty */
311 if($j('#time_start').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Time Start", close: function(){ /* */ $j('[name=time_start]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
312 /* Field time_end can't be empty */
313 if($j('#time_end').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Time End", close: function(){ /* */ $j('[name=time_end]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
314 /* Field unit_code can't be empty */
315 if($j('#unit_code').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Unit code", close: function(){ /* */ $j('[name=unit_code]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
316 /* Field venue can't be empty */
317 if($j('#venue').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Venue", close: function(){ /* */ $j('[name=venue]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
318 /* Field school can't be empty */
319 if($j('#school').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> School", close: function(){ /* */ $j('[name=school]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
320 /* Field department can't be empty */
321 if($j('#department').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Department", close: function(){ /* */ $j('[name=department]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
322 /* Field year_of_study can't be empty */
323 if($j('#year_of_study').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Year of study", close: function(){ /* */ $j('[name=year_of_study]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
324 return true;
325 }
326 function personal_time_table_validateData(){
327 $j('.has-error').removeClass('has-error');
328 /* Field day can't be empty */
329 if($j('#day').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Day", close: function(){ /* */ $j('[name=day]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
330 /* Field time_start can't be empty */
331 if($j('#time_start').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Time Start", close: function(){ /* */ $j('[name=time_start]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
332 /* Field time_end can't be empty */
333 if($j('#time_end').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Time End", close: function(){ /* */ $j('[name=time_end]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
334 /* Field activity can't be empty */
335 if($j('#activity').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Activity", close: function(){ /* */ $j('[name=activity]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
336 return true;
337 }
338 function student_details_validateData(){
339 $j('.has-error').removeClass('has-error');
340 /* Field full_name can't be empty */
341 if($j('#full_name').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Full name", close: function(){ /* */ $j('[name=full_name]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
342 /* Field school can't be empty */
343 if($j('#school').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> School", close: function(){ /* */ $j('[name=school]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
344 /* Field department can't be empty */
345 if($j('#department').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Department", close: function(){ /* */ $j('[name=department]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
346 /* Field year_of_study can't be empty */
347 if($j('#year_of_study').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Year of study", close: function(){ /* */ $j('[name=year_of_study]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
348 /* Field reg_no can't be empty */
349 if($j('#reg_no').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Reg no", close: function(){ /* */ $j('[name=reg_no]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
350 return true;
351 }
352 function notices_validateData(){
353 $j('.has-error').removeClass('has-error');
354 /* Field notice can't be empty */
355 if($j('#notice').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Notice", close: function(){ /* */ $j('[name=notice]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
356 /* Field school can't be empty */
357 if($j('#school').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> School", close: function(){ /* */ $j('[name=school]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
358 /* Field department can't be empty */
359 if($j('#department').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Department", close: function(){ /* */ $j('[name=department]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
360 /* Field year_of_study can't be empty */
361 if($j('#year_of_study').val() == ''){ modal_window({ message: '<div class="alert alert-danger"><?php echo addslashes($Translation['field not null']); ?></div>', title: "<?php echo addslashes($Translation['error:']); ?> Year of study", close: function(){ /* */ $j('[name=year_of_study]').eq(0).focus().parents('.form-group').addClass('has-error'); }, footer: [{ label: '<?php echo addslashes($Translation['ok']); ?>' }] }); return false; };
362 return true;
363 }
364
365 function post(url, params, update, disable, loading, success_callback){
366 $j.ajax({
367 url: url,
368 type: 'POST',
369 data: params,
370 beforeSend: function() {
371 if($j('#' + disable).length) $j('#' + disable).prop('disabled', true);
372 if($j('#' + loading).length && update != loading) $j('#' + loading).html('<div style="direction: ltr;"><img src="loading.gif"> <?php echo addslashes($Translation['Loading ...']); ?></div>');
373 },
374 success: function(resp) {
375 if($j('#' + update).length) $j('#' + update).html(resp);
376 if(success_callback != undefined) success_callback();
377 },
378 complete: function() {
379 if($j('#' + disable).length) $j('#' + disable).prop('disabled', false);
380 if($j('#' + loading).length && loading != update) $j('#' + loading).html('');
381 }
382 });
383 }
384
385 function post2(url, params, notify, disable, loading, redirectOnSuccess){
386 new Ajax.Request(
387 url, {
388 method: 'post',
389 parameters: params,
390 onCreate: function() {
391 if($(disable) != undefined) $(disable).disabled=true;
392 if($(loading) != undefined) $(loading).show();
393 },
394 onSuccess: function(resp) {
395 /* show notification containing returned text */
396 if($(notify) != undefined) $(notify).removeClassName('Error').appear().update(resp.responseText);
397
398 /* in case no errors returned, */
399 if(!resp.responseText.match(/<?php echo $Translation['error:']; ?>/)){
400 /* redirect to provided url */
401 if(redirectOnSuccess != undefined){
402 window.location=redirectOnSuccess;
403
404 /* or hide notification after a few seconds if no url is provided */
405 }else{
406 if($(notify) != undefined) window.setTimeout(function(){ /* */ $(notify).fade(); }, 15000);
407 }
408
409 /* in case of error, apply error class */
410 }else{
411 $(notify).addClassName('Error');
412 }
413 },
414 onComplete: function() {
415 if($(disable) != undefined) $(disable).disabled=false;
416 if($(loading) != undefined) $(loading).hide();
417 }
418 }
419 );
420 }
421 function passwordStrength(password, username){
422 // score calculation (out of 10)
423 var score = 0;
424 re = new RegExp(username, 'i');
425 if(username.length && password.match(re)) score -= 5;
426 if(password.length < 6) score -= 3;
427 else if(password.length > 8) score += 5;
428 else score += 3;
429 if(password.match(/(.*[0-9].*[0-9].*[0-9])/)) score += 3;
430 if(password.match(/(.*[!,@,#,$,%,^,&,*,?,_,~].*[!,@,#,$,%,^,&,*,?,_,~])/)) score += 5;
431 if(password.match(/([a-z].*[A-Z])|([A-Z].*[a-z])/)) score += 2;
432
433 if(score >= 9)
434 return 'strong';
435 else if(score >= 5)
436 return 'good';
437 else
438 return 'weak';
439 }
440 function validateEmail(email) {
441 var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
442 return re.test(email);
443 }
444 function loadScript(jsUrl, cssUrl, callback){
445 // adding the script tag to the head
446 var head = document.getElementsByTagName('head')[0];
447 var script = document.createElement('script');
448 script.type = 'text/javascript';
449 script.src = jsUrl;
450
451 if(cssUrl != ''){
452 var css = document.createElement('link');
453 css.href = cssUrl;
454 css.rel = "stylesheet";
455 css.type = "text/css";
456 head.appendChild(css);
457 }
458
459 // then bind the event to the callback function
460 // there are several events for cross browser compatibility
461 if(script.onreadystatechange != undefined){ script.onreadystatechange = callback; }
462 if(script.onload != undefined){ script.onload = callback; }
463
464 // fire the loading
465 head.appendChild(script);
466 }
467 /**
468 * options object. The following members can be provided:
469 * url: iframe url to load
470 * message: instead of a url to open, you could pass a message. HTML tags allowed.
471 * id: id attribute of modal window. auto-generated if not provided
472 * title: optional modal window title
473 * size: 'default', 'full'
474 * close: optional function to execute on closing the modal
475 * footer: optional array of objects describing the buttons to display in the footer.
476 * Each button object can have the following members:
477 * label: string, label of button
478 * bs_class: string, button bootstrap class. Can be 'primary', 'default', 'success', 'warning' or 'danger'
479 * click: function to execute on clicking the button. If the button closes the modal, this
480 * function is executed before the close handler
481 * causes_closing: boolean, default is true.
482 */
483 function modal_window(options){
484 return jQuery('body').agModal(options).agModal('show').attr('id');
485 }
486
487 function random_string(string_length){
488 var text = "";
489 var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
490
491 for(var i = 0; i < string_length; i++)
492 text += possible.charAt(Math.floor(Math.random() * possible.length));
493
494 return text;
495 }
496
497 /**
498 * @return array of IDs (PK values) of selected records in TV (records that the user checked)
499 */
500 function get_selected_records_ids(){
501 return jQuery('.record_selector:checked').map(function(){ /* */ return jQuery(this).val() }).get();
502 }
503
504 function print_multiple_dv_tvdv(t, ids){
505 document.myform.NoDV.value=1;
506 document.myform.PrintDV.value=1;
507 document.myform.SelectedID.value = '';
508 document.myform.submit();
509 return true;
510 }
511
512 function print_multiple_dv_sdv(t, ids){
513 document.myform.NoDV.value=1;
514 document.myform.PrintDV.value=1;
515 document.myform.writeAttribute('novalidate', 'novalidate');
516 document.myform.submit();
517 return true;
518 }
519
520 function mass_delete(t, ids){
521 if(ids == undefined) return;
522 if(!ids.length) return;
523
524 var confirm_message = '<div class="alert alert-danger">' +
525 '<i class="glyphicon glyphicon-warning-sign"></i> ' +
526 '<?php echo addslashes($Translation['<n> records will be deleted. Are you sure you want to do this?']); ?>' +
527 '</div>';
528 var confirm_title = '<?php echo addslashes($Translation['Confirm deleting multiple records']); ?>';
529 var label_yes = '<?php echo addslashes($Translation['Yes, delete them!']); ?>';
530 var label_no = '<?php echo addslashes($Translation['No, keep them.']); ?>';
531 var progress = '<?php echo addslashes($Translation['Deleting record <i> of <n>']); ?>';
532 var continue_delete = true;
533
534 // request confirmation of mass delete operation
535 modal_window({
536 message: confirm_message.replace(/\<n\>/, ids.length),
537 title: confirm_title,
538 footer: [ /* shows a 'yes' and a 'no' buttons .. handler for each follows ... */
539 {
540 label: '<i class="glyphicon glyphicon-trash"></i> ' + label_yes,
541 bs_class: 'danger',
542 // on confirming, start delete operations
543 click: function(){
544
545 // show delete progress, allowing user to abort operations by closing the window or clicking cancel
546 var progress_window = modal_window({
547 title: '<?php echo addslashes($Translation['Delete progress']); ?>',
548 message: '' +
549 '<div class="progress">' +
550 '<div class="progress-bar progress-bar-warning" role="progressbar" style="width: 0;"></div>' +
551 '</div>' +
552 '<button type="button" class="btn btn-default details_toggle" onclick="' +
553 'jQuery(this).children(\'.glyphicon\').toggleClass(\'glyphicon-chevron-right glyphicon-chevron-down\'); ' +
554 'jQuery(\'.well.details_list\').toggleClass(\'hidden\');'
555 + '">' +
556 '<i class="glyphicon glyphicon-chevron-right"></i> ' +
557 '<?php echo addslashes($Translation['Show/hide details']); ?>' +
558 '</button>' +
559 '<div class="well well-sm details_list hidden"><ol></ol></div>',
560 close: function(){
561 // stop deleting further records ...
562 continue_delete = false;
563 },
564 footer: [
565 {
566 label: '<i class="glyphicon glyphicon-remove"></i> <?php echo addslashes($Translation['Cancel']); ?>',
567 bs_class: 'warning'
568 }
569 ]
570 });
571
572 // begin deleting records, one by one
573 progress = progress.replace(/\<n\>/, ids.length);
574 var delete_record = function(itrn){
575 if(!continue_delete) return;
576 jQuery.ajax(t + '_view.php', {
577 type: 'POST',
578 data: { delete_x: 1, SelectedID: ids[itrn] },
579 success: function(resp){
580 if(resp == 'OK'){
581 jQuery(".well.details_list ol").append('<li class="text-success"><?php echo addslashes($Translation['The record has been deleted successfully']); ?></li>');
582 jQuery('#record_selector_' + ids[itrn]).prop('checked', false).parent().parent().fadeOut(1500);
583 jQuery('#select_all_records').prop('checked', false);
584 }else{
585 jQuery(".well.details_list ol").append('<li class="text-danger">' + resp + '</li>');
586 }
587 },
588 error: function(){
589 jQuery(".well.details_list ol").append('<li class="text-warning"><?php echo addslashes($Translation['Connection error']); ?></li>');
590 },
591 complete: function(){
592 jQuery('#' + progress_window + ' .progress-bar').attr('style', 'width: ' + (Math.round((itrn + 1) / ids.length * 100)) + '%;').html(progress.replace(/\<i\>/, (itrn + 1)));
593 if(itrn < (ids.length - 1)){
594 delete_record(itrn + 1);
595 }else{
596 if(jQuery('.well.details_list li.text-danger, .well.details_list li.text-warning').length){
597 jQuery('button.details_toggle').removeClass('btn-default').addClass('btn-warning').click();
598 jQuery('.btn-warning[id^=' + progress_window + '_footer_button_]')
599 .toggleClass('btn-warning btn-default')
600 .html('<?php echo addslashes($Translation['ok']); ?>');
601 }else{
602 setTimeout(function(){ /* */ jQuery('#' + progress_window).agModal('hide'); }, 500);
603 }
604 }
605 }
606 });
607 }
608
609 delete_record(0);
610 }
611 },
612 {
613 label: '<i class="glyphicon glyphicon-ok"></i> ' + label_no,
614 bs_class: 'success'
615 }
616 ]
617 });
618 }
619
620 function mass_change_owner(t, ids){
621 if(ids == undefined) return;
622 if(!ids.length) return;
623
624 var update_form = '<?php echo addslashes($Translation['Change owner of <n> selected records to']); ?> ' +
625 '<span id="new_owner_for_selected_records"></span><input type="hidden" name="new_owner_for_selected_records" value="">';
626 var confirm_title = '<?php echo addslashes($Translation['Change owner']); ?>';
627 var label_yes = '<?php echo addslashes($Translation['Continue']); ?>';
628 var label_no = '<?php echo addslashes($Translation['Cancel']); ?>';
629 var progress = '<?php echo addslashes($Translation['Updating record <i> of <n>']); ?>';
630 var continue_updating = true;
631
632 // request confirmation of mass update operation
633 modal_window({
634 message: update_form.replace(/\<n\>/, ids.length),
635 title: confirm_title,
636 footer: [ /* shows a 'continue' and a 'cancel' buttons .. handler for each follows ... */
637 {
638 label: '<i class="glyphicon glyphicon-ok"></i> ' + label_yes,
639 bs_class: 'success',
640 // on confirming, start update operations
641 click: function(){
642 var memberID = jQuery('input[name=new_owner_for_selected_records]').eq(0).val();
643 if(!memberID.length) return;
644
645 // show update progress, allowing user to abort operations by closing the window or clicking cancel
646 var progress_window = modal_window({
647 title: '<?php echo addslashes($Translation['Update progress']); ?>',
648 message: '' +
649 '<div class="progress">' +
650 '<div class="progress-bar progress-bar-success" role="progressbar" style="width: 0;"></div>' +
651 '</div>' +
652 '<button type="button" class="btn btn-default details_toggle" onclick="' +
653 'jQuery(this).children(\'.glyphicon\').toggleClass(\'glyphicon-chevron-right glyphicon-chevron-down\'); ' +
654 'jQuery(\'.well.details_list\').toggleClass(\'hidden\');'
655 + '">' +
656 '<i class="glyphicon glyphicon-chevron-right"></i> ' +
657 '<?php echo addslashes($Translation['Show/hide details']); ?>' +
658 '</button>' +
659 '<div class="well well-sm details_list hidden"><ol></ol></div>',
660 close: function(){
661 // stop updating further records ...
662 continue_updating = false;
663 },
664 footer: [
665 {
666 label: '<i class="glyphicon glyphicon-remove"></i> <?php echo addslashes($Translation['Cancel']); ?>',
667 bs_class: 'warning'
668 }
669 ]
670 });
671
672 // begin updating records, one by one
673 progress = progress.replace(/\<n\>/, ids.length);
674 var update_record = function(itrn){
675 if(!continue_updating) return;
676 jQuery.ajax('admin/pageEditOwnership.php', {
677 type: 'POST',
678 data: {
679 pkValue: ids[itrn],
680 t: t,
681 memberID: memberID,
682 saveChanges: 'Save changes'
683 },
684 success: function(resp){
685 if(resp == 'OK'){
686 jQuery(".well.details_list ol").append('<li class="text-success"><?php echo addslashes($Translation['record updated']); ?></li>');
687 jQuery('#record_selector_' + ids[itrn]).prop('checked', false);
688 jQuery('#select_all_records').prop('checked', false);
689 }else{
690 jQuery(".well.details_list ol").append('<li class="text-danger">' + resp + '</li>');
691 }
692 },
693 error: function(){
694 jQuery(".well.details_list ol").append('<li class="text-warning"><?php echo addslashes($Translation['Connection error']); ?></li>');
695 },
696 complete: function(){
697 jQuery('#' + progress_window + ' .progress-bar').attr('style', 'width: ' + (Math.round((itrn + 1) / ids.length * 100)) + '%;').html(progress.replace(/\<i\>/, (itrn + 1)));
698 if(itrn < (ids.length - 1)){
699 update_record(itrn + 1);
700 }else{
701 if(jQuery('.well.details_list li.text-danger, .well.details_list li.text-warning').length){
702 jQuery('button.details_toggle').removeClass('btn-default').addClass('btn-warning').click();
703 jQuery('.btn-warning[id^=' + progress_window + '_footer_button_]')
704 .toggleClass('btn-warning btn-default')
705 .html('<?php echo addslashes($Translation['ok']); ?>');
706 }else{
707 jQuery('button.btn-warning[id^=' + progress_window + '_footer_button_]')
708 .toggleClass('btn-warning btn-success')
709 .html('<i class="glyphicon glyphicon-ok"></i> <?php echo addslashes($Translation['ok']); ?>');
710 }
711 }
712 }
713 });
714 }
715
716 update_record(0);
717 }
718 },
719 {
720 label: '<i class="glyphicon glyphicon-remove"></i> ' + label_no,
721 bs_class: 'warning'
722 }
723 ]
724 });
725
726 /* show drop down of users */
727 var populate_new_owner_dropdown = function(){
728
729 jQuery('[id=new_owner_for_selected_records]').select2({
730 width: '100%',
731 formatNoMatches: function(term){ /* */ return '<?php echo addslashes($Translation['No matches found!']); ?>'; },
732 minimumResultsForSearch: 10,
733 loadMorePadding: 200,
734 escapeMarkup: function(m){ /* */ return m; },
735 ajax: {
736 url: 'admin/getUsers.php',
737 dataType: 'json',
738 cache: true,
739 data: function(term, page){ /* */ return { s: term, p: page, t: t }; },
740 results: function(resp, page){ /* */ return resp; }
741 }
742 }).on('change', function(e){
743 jQuery('[name="new_owner_for_selected_records"]').val(e.added.id);
744 });
745
746 }
747
748 populate_new_owner_dropdown();
749 }
750
751 function add_more_actions_link(){
752 window.open('https://bigprof.com/appgini/help/advanced-topics/hooks/multiple-record-batch-actions?r=appgini-action-menu');
753 }
754
755 /* detect current screen size (xs, sm, md or lg) */
756 function screen_size(sz){
757 if(!$j('.device-xs').length){
758 $j('body').append(
759 '<div class="device-xs visible-xs"></div>' +
760 '<div class="device-sm visible-sm"></div>' +
761 '<div class="device-md visible-md"></div>' +
762 '<div class="device-lg visible-lg"></div>'
763 );
764 }
765 return $j('.device-' + sz).is(':visible');
766 }
767
768 /* enable floating of action buttons in DV so they are visible on vertical scrolling */
769 function enable_dvab_floating(){
770 /* already run? */
771 if(window.enable_dvab_floating_run != undefined) return;
772
773 /* scroll action buttons of DV on scrolling DV */
774 $j(window).scroll(function(){
775 if(!screen_size('md') && !screen_size('lg')) return;
776 if(!$j('.detail_view').length) return;
777
778 /* get vscroll amount, DV form height, button toolbar height and position */
779 var vscroll = $j(window).scrollTop();
780 var dv_height = $j('[id$="_dv_form"]').eq(0).height();
781 var bt_height = $j('.detail_view .btn-toolbar').height();
782 var form_top = $j('.detail_view .form-group').eq(0).offset().top;
783 var bt_top_max = dv_height - bt_height - 10;
784
785 if(vscroll > form_top){
786 var tm = parseInt(vscroll - form_top) + 60;
787 if(tm > bt_top_max) tm = bt_top_max;
788
789 $j('.detail_view .btn-toolbar').css({ 'margin-top': tm + 'px' });
790 }else{
791 $j('.detail_view .btn-toolbar').css({ 'margin-top': 0 });
792 }
793 });
794 window.enable_dvab_floating_run = true;
795 }
796
797 /* check if a given field's value is unique and reflect this in the DV form */
798 function enforce_uniqueness(table, field){
799 $j('#' + field).on('change', function(){
800 /* check uniqueness of field */
801 var data = {
802 t: table,
803 f: field,
804 value: $j('#' + field).val()
805 };
806
807 if($j('[name=SelectedID]').val().length) data.id = $j('[name=SelectedID]').val();
808
809 $j.ajax({
810 url: 'ajax_check_unique.php',
811 data: data,
812 complete: function(resp){
813 if(resp.responseJSON.result == 'ok'){
814 $j('#' + field + '-uniqueness-note').hide();
815 $j('#' + field).parents('.form-group').removeClass('has-error');
816 }else{
817 $j('#' + field + '-uniqueness-note').show();
818 $j('#' + field).parents('.form-group').addClass('has-error');
819 $j('#' + field).focus();
820 setTimeout(function(){ /* */ $j('#update, #insert').prop('disabled', true); }, 500);
821 }
822 }
823 })
824 });
825 }
826
827 /* persist expanded/collapsed chidren in DVP */
828 function persist_expanded_child(id){
829 var expand_these = Cookies.getJSON('Jisort.dvp_expand');
830 if(expand_these == undefined) expand_these = [];
831
832 if($j('[id=' + id + ']').hasClass('active')){
833 if(expand_these.indexOf(id) < 0){
834 // expanded button and not persisting in cookie? save it!
835 expand_these.push(id);
836 Cookies.set('Jisort.dvp_expand', expand_these, { expires: 30 });
837 }
838 }else{
839 if(expand_these.indexOf(id) >= 0){
840 // collapsed button and persisting in cookie? remove it!
841 expand_these.splice(expand_these.indexOf(id), 1);
842 Cookies.set('Jisort.dvp_expand', expand_these, { expires: 30 });
843 }
844 }
845 }
846
847 /* apply expanded/collapsed status to children in DVP */
848 function apply_persisting_children(){
849 var expand_these = Cookies.getJSON('Jisort.dvp_expand');
850 if(expand_these == undefined) return;
851
852 expand_these.each(function(id){
853 $j('[id=' + id + ']:not(.active)').click();
854 });
855 }
856
857 function select2_max_width_decrement(){
858 return ($j('div.container').eq(0).hasClass('theme-compact') ? 99 : 109);
859 }
860
861 /**
862 * @brief AppGini.TVScroll().more() to scroll one column more.
863 * AppGini.TVScroll().less() to scroll one column less.
864 */
865 AppGini.TVScroll = function(){
866
867 /**
868 * @brief Calculates the width of the first n columns of the TV table
869 *
870 * @param [in] n how many columns to calculate the width for
871 * @return Return total width of given n columns, or 0 if n < 1 or invalid
872 */
873 var _TVColsWidth = function(n){
874 if(isNaN(n)) return 0;
875 if(n < 1) return 0;
876
877 var tw = 0, cc;
878 for(var i = 0; i < n; i++){
879 cc = $j('.table_view .table th:visible').eq(i);
880 if(!cc.length) break;
881 tw += cc.outerWidth();
882 }
883
884 return tw;
885 };
886
887 /**
888 * @brief show/hide tv-scroll buttons based on whether TV is horizontally scrollable or not
889 * @details should be called once on document load before hiding TV columns (by calling less())
890 */
891 var toggle_tv_scroll_tools = function(){
892 var tr = $j('.table_view .table-responsive'),
893 vpw = tr.width(), // viewport width
894 tfw = tr.find('.table').width(); // full width of the table
895
896 if(vpw >= tfw) $j('.tv-scroll').parents('.btn-group').hide();
897 else $j('.tv-scroll').parents('.btn-group').show();
898 }
899
900 /**
901 * @brief Prepares variables for use by less & more
902 */
903 var _TVScrollSetup = function(){
904 if(AppGini._TVColsScrolled === undefined) AppGini._TVColsScrolled = 0;
905 AppGini._TVColsCount = $j('.table_view .table th:visible').length;
906
907 /* type of scrolling, https://github.com/othree/jquery.rtl-scroll-type */
908 /*
909 How to interpret AppGini._ScrollType?
910 {LTR | RTL}:{scrollLeft val for left position}:{scrollLeft val for right position}:{initial scrollLeft val}
911 */
912 if(AppGini._ScrollType === undefined){
913 /* all browsers behave the same on LTR */
914 AppGini._ScrollType = 'LTR:0:100:0';
915
916 if($j('.container').hasClass('theme-rtl')){
917 var definer = $j('<div dir="rtl" style="font-size: 14px; width: 4px; height: 1px; position: absolute; top: -1000px; overflow: scroll">ABCD</div>').appendTo('body')[0];
918
919 AppGini._ScrollType = 'RTL:100:0:0'; // IE
920 if(definer.scrollLeft > 0){
921 AppGini._ScrollType = 'RTL:0:100:70'; // WebKit
922 }else{
923 definer.scrollLeft = 1;
924 if(definer.scrollLeft === 0) AppGini._ScrollType = 'RTL:-100:0:0'; // Firefox/Opera
925 }
926 }
927
928 /* show/hide #tv-scroll buttons based on TV scroll state */
929 $j(window).resize(toggle_tv_scroll_tools);
930 toggle_tv_scroll_tools();
931 }
932 };
933
934 /**
935 * @brief Resets all scrolling and setup values.
936 * @details Useful after hiding/showing columns to re-setup TV scrolling
937 */
938 var reset = function(){
939 if(AppGini._ScrollType === undefined) return; // nothing to reset!
940 AppGini._TVColsScrolled = undefined;
941
942 var tr = $j('.table_view .table-responsive');
943 switch(AppGini._ScrollType){
944 case 'RTL:100:0:0':
945 case 'RTL:0:100:0':
946 case 'RTL:-100:0:0':
947 tr.scrollLeft(0);
948 break;
949 case 'RTL:0:100:70':
950 var vpw = tr.width(), // viewport width
951 tfw = tr.find('.table').width(); // full width of the table
952 tr.scrollLeft(tfw - vpw + 10);
953 break;
954 }
955
956 _TVScrollSetup();
957 };
958
959 var _TVScroll = function(){
960 var scroll = 0,
961 tr = $j('.table_view .table-responsive'),
962 cw = _TVColsWidth(AppGini._TVColsScrolled); // width of columns to scroll to
963
964 switch(AppGini._ScrollType){
965 case 'RTL:100:0:0':
966 case 'LTR:0:100:0':
967 scroll = cw - 1;
968 break;
969 case 'RTL:-100:0:0':
970 scroll = -1 * cw + 1;
971 break;
972 case 'RTL:0:100:70':
973 var vpw = tr.width(), // viewport width
974 tfw = tr.find('.table').width(); // full width of the table
975 scroll = tfw - vpw - cw + 1;
976 break;
977 }
978
979 tr.scrollLeft(scroll);
980 };
981
982 /**
983 * @brief Scroll the TV table 1 column more
984 */
985 var more = function(){
986 if(AppGini._TVColsScrolled >= AppGini._TVColsCount) return;
987 AppGini._TVColsScrolled++;
988 _TVScroll();
989 };
990
991 /**
992 * @brief Scroll the TV table 1 column less
993 */
994 var less = function(){
995 if(AppGini._TVColsScrolled <= 0) return;
996 AppGini._TVColsScrolled--;
997 _TVScroll();
998 };
999
1000 _TVScrollSetup();
1001
1002 return { more: more, less: less, reset: reset };
1003
1004 };
1005
1006 (function($j){
1007 /*
1008 apply a modal or an in-page modal to an element,
1009 or access modal methods/events if it's already 'modal'ed
1010
1011 Expected usage:
1012 1. $j('any_selector').agModal({ new modal options .. })
1013 2. $j('#modal_id').agModal('command')
1014 3. $j('#modal_id').on('event.bs.modal', event_handler)
1015
1016 case 1: the selector doesn't matter ... the modal will be created and attached
1017 to the body element .. to retrieve the modal id if not specified in options:
1018 var modal_id = $j('any_selector').agModal({ new modal options .. }).attr('id');
1019
1020 case 2: the selector must be the modal element .. if it's a standard BS modal,
1021 command will be passed as is to .modal() and the return value returned.
1022 if it's an in-page modal, command will be emulated and the modal element
1023 returned.
1024
1025 case 3: Bootstrap modal events.
1026 */
1027 $j.fn.agModal = function(options){
1028 var theModal = this,
1029 open = function(){
1030 return theModal.trigger('show.bs.modal').removeClass('hide').trigger('shown.bs.modal');
1031 },
1032 close = function(){
1033 return theModal.trigger('hide.bs.modal').addClass('hide').trigger('hidden.bs.modal');
1034 };
1035
1036 if(typeof(options) == 'string'){
1037 if(theModal.hasClass('modal')) return theModal.modal(options);
1038 if(!theModal.hasClass('inpage-modal')) return theModal;
1039
1040 /* emulate .modal(command) for the in-page modal */
1041 switch(options){
1042 case 'show':
1043 open();
1044 break;
1045 case 'hide':
1046 close();
1047 break;
1048 }
1049
1050 return theModal;
1051 }
1052
1053 var op = $j.extend({
1054 /* default options */
1055 id: random_string(20),
1056 footer: [],
1057 extras: {},
1058 size: 'default',
1059 forceIPM: false
1060 }, options);
1061
1062 if(op.url == undefined && op.message == undefined){
1063 console.error('Missing message/url in call to AppGini.modal().');
1064 return theModal;
1065 }
1066
1067 var iOS = /(iPad|iPhone|iPod)/g.test(navigator.userAgent), /* true for iOS devices */
1068 auto_id = (options.id === undefined), /* true if modal id is auto-generated */
1069
1070 _resize = function(id){
1071 var mod = $j('#' + id);
1072 if(!mod.length) return;
1073
1074 var ipm = (mod.hasClass('inpage-modal') ? '.inpage-modal-' : '.modal-');
1075
1076 var wh = $j(window).height(),
1077 mtm = mod.find(ipm + 'dialog').css('margin-top'),
1078 mhfoh = mod.find(ipm + 'header').outerHeight() + mod.find(ipm + 'footer').outerHeight();
1079
1080 mod.find(ipm + 'dialog').css({
1081 margin: mtm,
1082 width: 'calc(100% - 2 * ' + mtm + ')'
1083 });
1084
1085 mod.find(ipm + 'body').css({
1086 height: 'calc(' + wh + 'px - ' + mhfoh + 'px - 2 * ' + mtm + ' - 6px)'
1087 });
1088 },
1089
1090 _bsModal = function(){
1091 /* build the html of footer buttons into footer_buttons variable */
1092 var footer_buttons = '';
1093 for(i = 0; i < op.footer.length; i++){
1094 if(typeof(op.footer[i].label) != 'string') continue;
1095
1096 op.footer[i] = $j.extend(
1097 /* defaults */
1098 {
1099 causes_closing: true,
1100 bs_class: 'default'
1101 },
1102 op.footer[i],
1103 /* enforce the following values */
1104 { id: op.id + '_footer_button_' + random_string(10) }
1105 );
1106
1107 footer_buttons += '<button ' +
1108 'type="button" ' +
1109 'class="btn btn-' + op.footer[i].bs_class + '" ' +
1110 (op.footer[i].causes_closing ? 'data-dismiss="modal" ' : '') +
1111 'id="' + op.footer[i].id + '" ' +
1112 '>' + op.footer[i].label +
1113 '</button>';
1114 }
1115
1116 var mod = $j(
1117 '<div class="modal fade" tabindex="-1" role="dialog" id="' + op.id + '">' +
1118 '<div class="modal-dialog" role="document">' +
1119 '<div class="modal-content">' +
1120 ( op.title != undefined ?
1121 '<div class="modal-header">' +
1122 '<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>' +
1123 '<h4 class="modal-title" style="width: 90%;">' + op.title + '</h4>' +
1124 '</div>'
1125 : ''
1126 ) +
1127 '<div class="modal-body">' +
1128 ( op.url != undefined ?
1129 '<iframe ' +
1130 'width="100%" height="100%" ' +
1131 'style="display: block; overflow: scroll !important; -webkit-overflow-scrolling: touch !important;" ' +
1132 'sandbox="allow-modals allow-forms allow-scripts allow-same-origin allow-popups" ' +
1133 'src="' + op.url + '">' +
1134 '</iframe>'
1135 : op.message
1136 ) +
1137 '</div>' +
1138 '<div class="modal-footer">' + footer_buttons + '</div>' +
1139 '</div>' +
1140 '</div>' +
1141 '</div>'
1142 );
1143
1144 if(op.url != undefined){
1145 mod.find('.modal-body').css('padding', '0');
1146 }
1147
1148 return mod;
1149 },
1150
1151 _ipModal = function(){
1152 /* prepare footer buttons, if any */
1153 var footer_buttons = '', closer_class = '';
1154 for(i = 0; i < op.footer.length; i++){
1155 if(typeof(op.footer[i].label) != 'string') continue;
1156
1157 if(op.footer[i].causes_closing !== false){ op.footer[i].causes_closing = true; }
1158 op.footer[i].bs_class = op.footer[i].bs_class || 'default';
1159 op.footer[i].id = op.id + '_footer_button_' + random_string(10);
1160
1161 closer_class = (op.footer[i].causes_closing ? ' closes-inpage-modal' : '');
1162
1163 footer_buttons += '<button type="button" ' +
1164 'class="hspacer-lg vspacer-lg btn btn-' + op.footer[i].bs_class + closer_class + '" ' +
1165 'id="' + op.footer[i].id + '" ' +
1166 '>' + op.footer[i].label + '</button>';
1167 }
1168
1169 var imc = $j(
1170 '<div id="' + op.id + '" ' +
1171 'class="inpage-modal hide ' + $j('.container').eq(0).attr('class') + '" ' +
1172 'style="' +
1173 'padding-left: 0; padding-right: 0;' +
1174 'width: 100% !important;' +
1175 '">' +
1176 '<div ' +
1177 'class="inpage-modal-dialog" ' +
1178 'style="' +
1179 'box-shadow: 0 0 61px 15px #666;' +
1180 'margin: 10px !important;' +
1181 'border: solid 1px;' +
1182 'border-radius: 5px;' +
1183 '">' +
1184 '<div class="inpage-modal-content">' +
1185 ( op.title != undefined ?
1186 '<div class="inpage-modal-header" style="border-bottom: solid 1px;">' +
1187 '<div class="row" style="margin: 0;">' +
1188 '<div class="col-xs-10 col-sm-11 inpage-modal-title">' +
1189 '<div class="h4">' + op.title + '</div>' +
1190 '</div>' +
1191 '<div class="col-xs-2 col-sm-1 closes-inpage-modal text-center inpage-modal-dismiss" style="cursor: pointer;">' +
1192 '<h4 class="glyphicon glyphicon-remove"></h4>' +
1193 '</div>' +
1194 '</div>' +
1195 '</div>'
1196 : ''
1197 ) +
1198
1199 ( footer_buttons.length ?
1200 '<div class="inpage-modal-footer text-right flip" style="border-bottom: solid 1px;">' + footer_buttons + '</div>'
1201 : ''
1202 ) +
1203
1204 '<div class="inpage-modal-body">' +
1205 ( op.url != undefined ?
1206 '<iframe ' +
1207 'width="100%" height="100%" ' +
1208 'style="display: block; overflow: scroll !important; -webkit-overflow-scrolling: touch !important;" ' +
1209 'sandbox="allow-modals allow-forms allow-scripts allow-same-origin allow-popups" ' +
1210 'src="' + op.url + '">' +
1211 '</iframe>'
1212 : op.message
1213 ) +
1214 '</div>' +
1215 '</div>' +
1216 '</div>' +
1217 '</div>'
1218 );
1219
1220 /* hover effect for dismiss button + close modal if a closer clicked */
1221 imc.on('mouseover', '.inpage-modal-dismiss', function(){
1222 $j(this).addClass('text-danger bg-danger');
1223 }).on('mouseout', '.inpage-modal-dismiss', function(){
1224 $j(this).removeClass('text-danger bg-danger');
1225 }).on('click', '.closes-inpage-modal', close);
1226
1227 imc.find('.inpage-modal-title').css({
1228 overflow: 'auto',
1229 'white-space': 'nowrap'
1230 });
1231
1232 return imc;
1233 };
1234
1235 /* if modal exists, remove it first */
1236 $j('#' + op.id).remove();
1237
1238 theModal = ((iOS || op.forceIPM) && op.size == 'full' ? _ipModal() : _bsModal());
1239
1240 theModal.appendTo('body');
1241
1242 /* bind footer buttons click handlers */
1243 for(i = 0; i < op.footer.length; i++){
1244 if(typeof(op.footer[i].click) == 'function'){
1245 $j('#' + op.footer[i].id).click(op.footer[i].click);
1246 }
1247 }
1248
1249 theModal
1250 .on('show.bs.modal', function(){
1251 if(op.size != 'full') return;
1252
1253 /* hide main page to avoid all scrolling/panning hell on touch screens! */
1254 $j('.container').eq(0).hide();
1255 })
1256 .on('shown.bs.modal', function(){
1257 if(op.size != 'full') return;
1258
1259 var id = op.id, rsz = _resize;
1260 rsz(id);
1261 $j(window).resize(function(){ /* */ rsz(id); });
1262 })
1263 //.agModal('show')
1264 .on('hidden.bs.modal', function(){
1265 /* display main page again */
1266 if(op.size == 'full') $j('.container').eq(0).show();
1267
1268 if(typeof(op.close) == 'function'){
1269 op.close();
1270 }
1271
1272 if(!auto_id) return;
1273
1274 /* if id is automatic, remove modal after 1 minute from DOM */
1275 var id = op.id;
1276 var auto_remove = setInterval(function(){
1277 if($j('#' + id).is(':visible')) return; // don't remove if visible
1278 $j('#' + id).remove();
1279 clearInterval(auto_remove);
1280 }, 60000);
1281 });
1282
1283 return theModal;
1284 };
1285 })(jQuery);
1286
1287 /**
1288 * @brief Used in pages loaded inside modals (e.g. those with Embedded=1) to close the containing modal.
1289 */
1290 AppGini.closeParentModal = function(){
1291 var pm = window.parent.jQuery(".modal:visible");
1292 if(!pm.length){
1293 pm = window.parent.jQuery(".inpage-modal:visible");
1294 }
1295
1296 if(pm.length) pm.agModal('hide');
1297 return;
1298 }
1299
1300 /**
1301 * @return boolean indicating whether a modal is currently open or not
1302 */
1303 AppGini.modalOpen = function(){ /* */
1304 return jQuery('.modal-dialog:visible').length > 0 || jQuery('.inpage-modal-dialog:visible').length > 0;
1305 };
1306
1307
1308 /**
1309 * @return true for mobile devices, false otherwise
1310 * @details https://stackoverflow.com/a/11381730/1945185
1311 */
1312 AppGini.mobileDevice = function(){ /* */
1313 var check = false;
1314 (function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4))) check = true;})(navigator.userAgent||navigator.vendor||window.opera);
1315 return check;
1316 };
1317
1318 AppGini.datetimeFormat = function(datetime){ /* */
1319 if(undefined == datetime) datetime = 'd';
1320
1321 var dateFormat = 'MM/DD/YYYY';
1322 var timeFormat = 'hh:mm:ss A';
1323
1324 if(datetime.match(/(dt|td)/i)) return dateFormat + ' ' + timeFormat;
1325 if(datetime.match(/t/i)) return timeFormat;
1326 return dateFormat;
1327 };
1328
1329 AppGini.hideViewParentLinks = function() {
1330 /* find and hide parent links if field label has data 'parent_link' set to 'view_parent_hidden' */
1331 $j('label[data-parent_link=view_parent_hidden]').each(function(){
1332 $j(this).parents('.form-group').find('.view_parent').hide();
1333 });
1334 };